重开仓库内容:改推五个技能(browser-harness / humanizer / humanizer-zh / product-planning / session-mechanism)

按授权清空原有内容后重新提交(原 oil-ui-pro 一并移出,可从历史恢复)。
browser-harness 剔除 .venv 等运行环境;根 .gitignore 补记 .venv/ 与 node_modules/。
This commit is contained in:
admin committed 2026-10-07 16:06:02 +08:00
1 parent 9a152a1952
commit 777f7fe5d0
240 files changed
+52518 -4409

No files matched your search

@@ -0,0 +1,285 @@
# Flipkart — Gift & Product Shopping (Field-Tested)
Field-tested against flipkart.com on 2026-04-27/28 using `browser-harness` CDP
helpers (`goto_url`, `js`, `click_at_xy`, `type_text`, `capture_screenshot`,
`scroll`, `press_key`, `page_info`).
---
## TL;DR
**Build your search via URL parameters** with price and category filters baked
in. Dismiss the login popup immediately on every new session. For delivery
checks, enter the pincode on the product page — but note that changing pincode
may require login on some pages. Use `__NEXT_DATA__` JSON extraction when
available, fall back to DOM selectors.
---
## Gift Search URL Template
```
https://www.flipkart.com/search?q={query}&marketplace=FLIPKART&p%5B%5D=facets.price_range.from%3DMin&p%5B%5D=facets.price_range.to%3D{max_price}
```
### Example — Gifts for kids under ₹500:
```python
goto_url("https://www.flipkart.com/search?"
"q=gifts+for+kids+under+10+years"
"&marketplace=FLIPKART"
"&p%5B%5D=facets.price_range.from%3DMin"
"&p%5B%5D=facets.price_range.to%3D500")
```
### Example — Toys under ₹200, sorted by price:
```python
goto_url("https://www.flipkart.com/search?"
"q=toys+for+kids"
"&sort=price_asc"
"&p%5B%5D=facets.price_range.from%3DMin"
"&p%5B%5D=facets.price_range.to%3D200")
```
---
## URL Parameters Reference
| Parameter | Type | Example | Notes |
|-----------|------|---------|-------|
| `q` | string | `gifts+for+kids` | Search query |
| `page` | int | `2` | Pagination (1-based) |
| `sort` | string | `price_asc` | `relevance`, `price_asc`, `price_desc`, `recency_desc`, `popularity` |
| `marketplace` | string | `FLIPKART` | Restrict to Flipkart marketplace |
| `p[]` | string | `facets.price_range.from=Min` | Min price filter |
| `p[]` | string | `facets.price_range.to=500` | Max price filter |
| `p[]` | string | `facets.brand=Samsung` | Brand filter |
| `p[]` | string | `facets.rating=4` | Minimum rating |
**Note:** `p[]` parameters must be URL-encoded as `p%5B%5D=facets.price_range.to%3D500`.
---
## Login Popup — MUST DISMISS FIRST
Flipkart shows a login/signup modal on **every new session**. It blocks all
interaction until dismissed. This is the #1 cause of automation failures.
```python
js("""(()=>{
var btn = document.querySelector('button._2KpZ6l._2doB4z')
|| document.querySelector('[class*="close-btn"]')
|| document.querySelector('button[aria-label="Close"]');
if (btn) { btn.click(); return 'closed'; }
var overlay = document.querySelector('._2fS1Rz, ._3_UOR0');
if (overlay) { overlay.click(); return 'overlay_dismissed'; }
document.dispatchEvent(new KeyboardEvent('keydown', {key:'Escape', keyCode:27}));
return 'escape_sent';
})()""")
```
If the modal persists after JS dismiss, use coordinate click on the ✕ button
(typically top-right of the modal):
```python
click_at_xy(720, 180) # adjust based on screenshot
```
---
## Delivery Check by Pincode
### On search results page
Flipkart search results show estimated delivery per product. To check delivery
for a specific pincode, you must visit individual product pages.
### On product detail page
The delivery/pincode input is typically near the "Add to Cart" button:
```python
# Step 1: Find and click the pincode input or "Change" link
js("""(()=>{
var change = [...document.querySelectorAll('span, a')].find(
e => e.textContent.includes('Change') && e.closest('[class*="pincode"], [class*="delivery"]')
);
if (change) { change.click(); return 'clicked change'; }
var input = document.querySelector('input[placeholder*="pincode"], input[placeholder*="Pincode"]');
if (input) { input.focus(); input.select(); return 'focused input'; }
return 'not found';
})()""")
# Step 2: Clear and type new pincode
type_text("560077")
press_key("Enter")
# Step 3: Wait and read delivery estimate
wait(3)
delivery = js("""(()=>{
var el = document.querySelector('[class*="delivery"], [class*="shipping"]');
return el ? el.innerText.trim() : 'not found';
})()""")
```
**Gotcha:** Changing pincode on some product pages requires login. If you see a
login prompt after entering pincode, the delivery check won't work without
authentication. In this case, use the default delivery estimate shown on the
search results page.
---
## Product Evaluation Workflow
For gift shopping, evaluate products on these criteria:
```python
# After navigating to a product page:
evaluation = js("""(()=>{
var price = document.querySelector('div._30jeq3, [class*="selling-price"]')?.innerText?.trim();
var rating = document.querySelector('div._3LWZlK')?.innerText?.trim();
var ratingCount = document.querySelector('span._2_R_DZ')?.innerText?.trim();
var highlights = Array.from(document.querySelectorAll('li._21Ahn-'))
.map(e => e.innerText.trim()).filter(Boolean);
var delivery = document.querySelector('[class*="delivery"]')?.innerText?.trim();
var inStock = !document.querySelector('[class*="out-of-stock"], [class*="sold-out"]');
var seller = document.querySelector('[id*="seller"] a, div._3enH42')?.innerText?.trim();
return JSON.stringify({price, rating, ratingCount, highlights, delivery, inStock, seller});
})()""")
```
### Gift suitability checklist:
1. **Price** — within budget (parse `₹` and Indian comma format)
2. **Rating** — prefer 4.0+ with 50+ ratings
3. **Delivery** — check pincode delivery estimate (within required days)
4. **Availability** — ensure "In Stock" / "Add to Cart" visible
5. **Reusability** — read highlights/description for durability indicators
---
## Filtering on Search Results Page
### By price (via sidebar click)
If you need to adjust price after page load:
```python
# Flipkart's price filter is in the left sidebar
# Look for price range inputs
js("""(()=>{
var inputs = document.querySelectorAll('input[class*="price"], input[name*="price"]');
return inputs.length + ' price inputs found';
})()""")
```
### By rating (via sidebar click)
```python
js("""(()=>{
var stars = [...document.querySelectorAll('div, label')].find(
e => e.textContent.includes('4★') && e.closest('[class*="filter"]')
);
if (stars) { stars.click(); return 'clicked 4-star filter'; }
return 'not found';
})()""")
```
### By availability / delivery speed
Flipkart doesn't always expose delivery speed as a sidebar filter on all pages.
When available, look for "Delivery in X days" checkboxes.
---
## Data Extraction from Search Results
### Strategy 1: `__NEXT_DATA__` (Preferred)
```python
import json
nd_raw = js("document.getElementById('__NEXT_DATA__')?.textContent")
if nd_raw:
nd = json.loads(nd_raw)
# Walk the JSON tree to find product arrays
def find_products(obj, depth=0):
if depth > 8: return None
if isinstance(obj, list) and len(obj) > 3:
if isinstance(obj[0], dict) and any(k in obj[0] for k in ['title','name','price','productId']):
return obj
if isinstance(obj, dict):
for v in obj.values():
result = find_products(v, depth + 1)
if result: return result
return None
items = find_products(nd)
```
### Strategy 2: DOM Selectors (Fallback)
```python
results = js("""(()=>{
var links = Array.from(document.querySelectorAll('a[href*="/p/"]'));
var seen = new Set();
return links.map(a => {
var href = a.getAttribute('href');
if (seen.has(href)) return null;
seen.add(href);
var card = a.closest('[data-id]') || a.closest('div[class]');
if (!card) return null;
return {
title: card.querySelector('div._4rR01T, a.IRpwTa, a.s1Q9rs')?.innerText?.trim()
|| a.getAttribute('title')
|| card.querySelector('img')?.getAttribute('alt'),
price: card.querySelector('div._30jeq3')?.innerText?.trim(),
rating: card.querySelector('div._3LWZlK')?.innerText?.trim(),
url: 'https://www.flipkart.com' + href,
};
}).filter(Boolean);
})()""")
```
---
## Key Lessons (Field-Tested)
1. **URL-first strategy** — Encode search query, price range, sort order, and
brand filters directly in the URL. Avoid interacting with sidebar filters
when possible — they trigger React re-renders that can reset scroll position.
2. **Login popup is mandatory** — Every new browser session hits the login modal.
Dismiss it before ANY other interaction. If JS dismiss fails, use a
coordinate click on the close button.
3. **Pincode delivery check has limitations** — Changing delivery pincode on
product pages may require login. The search results page shows a default
delivery estimate that works without login.
4. **Image overlay traps** — Product pages have image zoom overlays that can
capture clicks intended for other elements. If interactions stop working,
press `Escape` first to dismiss any overlays, then retry.
5. **Indian price format** — Prices use `₹` prefix with Indian grouping:
`₹1,23,456` = 123,456 INR. Parse: `.replace(/[₹,]/g, '')`.
6. **Wait 3s after `wait_for_load()`** — Product cards load asynchronously via
React. `readyState=complete` fires before cards render.
7. **Use `new_tab()` for first Flipkart load** — `goto_url()` on the first visit
can silently fail if the current tab resists navigation.
8. **Flipkart blocks datacenter IPs** — Remote/headless browsers from cloud
providers (e.g., Playwright Service) get `ERR_CONNECTION_TIMED_OUT`. Flipkart
only works from residential IPs (local Chrome). For cloud-based automation,
consider alternative sites like FirstCry for kids' products.
9. **Category browse pages** — Use `sid` parameter for category navigation:
`https://www.flipkart.com/toys/pr?sid=tng` for toys. Combine with price
facets for filtered browsing.
10. **Obfuscated class names change frequently** — Flipkart uses Webpack-hashed
CSS classes (`_4rR01T`, `_30jeq3`). When selectors break, use
`a[href*="/p/"]` as the stable anchor and walk up to card containers.
Prefer `__NEXT_DATA__` extraction which is immune to class name changes.