239 lines
8.1 KiB
Markdown
239 lines
8.1 KiB
Markdown
# v4 Known Issues & Fixes
|
|
|
|
Session-record of bugs found and fixed during production Chefkoch scraping runs.
|
|
This file is referenced from the main `infinite-refill-scraper-v4.md` reference.
|
|
|
|
## Environment: Context for these fixes
|
|
|
|
- Host: Debian container, Python 3.11
|
|
- Playwright 1.60.0 installed in profile venv
|
|
- Browser cache: `/home/debian/.cache/ms-playwright/chromium-1223/`
|
|
- Background process launcher: Hermes Agent `terminal(background=True)`
|
|
- Cron: `chefkoch-scraper-watchdog` every 10 minutes (`no_agent=true`)
|
|
|
|
---
|
|
|
|
## Fix 1 — Playwright browser path not inherited
|
|
|
|
### Failure signature
|
|
|
|
```
|
|
playwright._impl._api_types.Error: Executable doesn't exist at
|
|
/home/debian/.cache/ms-playwright/chromium-1071/chrome-linux/chrome
|
|
```
|
|
|
|
Note: The error references a **different** chromium version (`-1071`) than what is actually installed (`-1223`). This mismatch happens because Playwright falls back to its own lookup logic when `PLAYWRIGHT_BROWSERS_PATH` is absent.
|
|
|
|
### Why it happens
|
|
|
|
Setting the env var in the Hermes Agent shell workspace does **not** propagate to the background bash launched by `terminal(background=True)`. The background process inherits `PATH` and `VIRTUAL_ENV` but not custom `export FOO=bar` values.
|
|
|
|
### Fix options (ranked)
|
|
|
|
1. **Best:** Hardcode inside `chefkoch_scraper_v4.py` before any Playwright import:
|
|
```python
|
|
import os
|
|
os.environ.setdefault("PLAYWRIGHT_BROWSERS_PATH", "/home/debian/.cache/ms-playwright")
|
|
```
|
|
|
|
2. **Acceptable:** Shell wrapper with `exec` and explicit `export`:
|
|
```bash
|
|
#! /bin/bash
|
|
export PLAYWRIGHT_BROWSERS_PATH=/home/debian/.cache/ms-playwright
|
|
exec python3 scripts/chefkoch_scraper_v4.py --no-agent >> scraper_v4.out 2>&1
|
|
```
|
|
|
|
3. **Do not:** Rely on cron env or parent-shell export alone.
|
|
|
|
---
|
|
|
|
## Fix 2 — `global SEED_CYCLE` missing
|
|
|
|
### Failure signature
|
|
|
|
Scraper runs every 10 minutes (cron restart) but recipe count never increases. Log shows:
|
|
|
|
```
|
|
START run | current: 12736/25000
|
|
```
|
|
|
|
State file `seed_cycle` is always `0`.
|
|
|
|
### Why it happens
|
|
|
|
Simplified pseudocode:
|
|
|
|
```python
|
|
SEED_CYCLE = 0 # module-level global
|
|
|
|
def _next_seeds(batch_size=50):
|
|
global SEED_CYCLE
|
|
for i in range(batch_size):
|
|
# ... compute seed based on SEED_CYCLE ...
|
|
SEED_CYCLE += 1
|
|
|
|
async def main():
|
|
state = _load_state()
|
|
SEED_CYCLE = state["seed_cycle"] # ← BUG: creates a local variable in main()
|
|
while ...:
|
|
seeds = _next_seeds(50) # reads/writes the GLOBAL SEED_CYCLE
|
|
# but the global is still 0 because main()'s assignment was local!
|
|
```
|
|
|
|
Because `SEED_CYCLE` is assigned inside `main()` without `global SEED_CYCLE`, Python creates a local variable shadowing the module global. `_next_seeds()` then reads the global (still 0), increments it, but on the next cron restart `main()` sets the local back to whatever is in state (`0`). The seeds are the same every single run.
|
|
|
|
### Fix
|
|
|
|
```python
|
|
async def main():
|
|
state = _load_state()
|
|
state.setdefault("seed_cycle", 0)
|
|
global SEED_CYCLE # ← required
|
|
SEED_CYCLE = state["seed_cycle"]
|
|
```
|
|
|
|
### Verification
|
|
|
|
```bash
|
|
python3 -c "
|
|
import chefkoch_scraper_v4 as m
|
|
m.main() # or simulate the assignment
|
|
print('global SEED_CYCLE after main:', m.SEED_CYCLE)
|
|
"
|
|
```
|
|
Without `global` it prints `0`. With `global` it prints whatever was in state.
|
|
|
|
---
|
|
|
|
## Fix 3 — Stale `completed_seeds` locking the scraper
|
|
|
|
### Failure signature
|
|
|
|
Scraper starts, opens browser, visits search pages, but `new_urls` is always empty because all returned URLs are already in `existing`. No new recipes are appended.
|
|
|
|
### Why it happens
|
|
|
|
`scraper_state.json` accumulated a `completed_seeds` dict during a multi-worker batch run. The v4 infinite-refill script does not use `completed_seeds`, but the state file still carries it from an older script version. Certain seed terms are effectively blacklisted.
|
|
|
|
### Fix
|
|
|
|
```python
|
|
import json
|
|
s = json.load(open("scraper_state.json"))
|
|
s.pop("completed_seeds", None)
|
|
# Advance seed_cycle past known-exhausted 2-letter alphabet seeds
|
|
s["seed_cycle"] = max(s.get("seed_cycle", 0), 1000)
|
|
json.dump(s, open("scraper_state.json", "w"), ensure_ascii=False, indent=2)
|
|
```
|
|
|
|
Setting `seed_cycle >= 1000` pushes the generator into the category+letter and category-only strategies, which yield far more fresh URLs than the `aa`, `ab`, `ac` alphabet exhaustion zone.
|
|
|
|
---
|
|
|
|
## Fix 4 — Dual script / dual Python trap (watchdog silently fails)
|
|
|
|
### Failure signature
|
|
|
|
Scraper cron log shows `Scraper started (PID NNN)` every 10 minutes. No errors ever appear. `scraper_v4.out` only contains `Starting: X/Y` line (the `print()` from `main()` before Playwright is used). Recipe count stays flat for days or weeks.
|
|
|
|
### Why it happens
|
|
|
|
The watchdog script (`~/.hermes/scripts/start_scraper_v4.sh`) uses:
|
|
1. **Global `python3`** → Playwright 1.59.0 (looks for `chromium-1071`, which does not exist in this environment).
|
|
2. **Global script** (`~/.hermes/scripts/chefkoch_scraper_v4.py`) → missing `global SEED_CYCLE` in `main()`.
|
|
|
|
Both of these individually produce crashes (Fix 1 + Fix 2 above). Combined, the global python immediately crashes on browser launch, outputting nothing useful to the log. The watchdog sees the PID exit, and restarts it again 10 minutes later. This loop repeats forever with zero recipes.
|
|
|
|
Meanwhile, a **working** copy exists at:
|
|
- `~/.hermes/profiles/nutrition-coach/.venv/bin/python3` (Playwright 1.60.0+, matches installed browsers)
|
|
- `~/.hermes/profiles/nutrition-coach/scripts/chefkoch_scraper_v4.py` (has `global SEED_CYCLE`)
|
|
|
|
Both copies look very similar from a distance, making it easy to invoke the wrong pair.
|
|
|
|
### Fix
|
|
|
|
Replace the watchdog shell script with the version that explicitly points to the profile-local python and script:
|
|
|
|
```bash
|
|
nohup /home/debian/.hermes/profiles/nutrition-coach/.venv/bin/python3 \
|
|
/home/debian/.hermes/profiles/nutrition-coach/scripts/chefkoch_scraper_v4.py \
|
|
--no-agent >> "$LOG" 2>&1 &
|
|
```
|
|
|
|
Also verify there is no stale PID file (otherwise the wrapper exits early assuming it's already running):
|
|
|
|
```bash
|
|
cat /tmp/chefkoch_scraper_v4.pid 2>/dev/null && (kill -0 "$(cat /tmp/chefkoch_scraper_v4.pid)" 2>/dev/null || rm -f /tmp/chefkoch_scraper_v4.pid)
|
|
```
|
|
|
|
### Detection
|
|
|
|
```bash
|
|
# 1. Check if log ever got past the first line
|
|
wc -l ~/.hermes/profiles/nutrition-coach/scraper_v4.out
|
|
# Unhealthy: 1 line forever
|
|
|
|
# 2. Check which python the startup script actually calls
|
|
grep 'python3' ~/.hermes/scripts/start_scraper_v4.sh
|
|
# Unhealthy: just "python3 ..." (no venv path)
|
|
|
|
# 3. Check which script path it points to
|
|
grep 'chefkoch_scraper' ~/.hermes/scripts/start_scraper_v4.sh
|
|
# Unhealthy: ~/.hermes/scripts/chefkoch_scraper_v4.py
|
|
```
|
|
|
|
### Recovery after fix
|
|
|
|
After updating the watchdog script:
|
|
|
|
```bash
|
|
# 1. Kill any stale scraper
|
|
pkill -f chefkoch_scraper_v4.py || true
|
|
rm -f /tmp/chefkoch_scraper_v4.pid
|
|
|
|
# 2. Advance state past exhaustion
|
|
python3 -c "
|
|
import json, os
|
|
p = os.path.expanduser('~/.hermes/profiles/nutrition-coach/scraper_state.json')
|
|
s = json.load(open(p))
|
|
s.pop('completed_seeds', None)
|
|
s['seed_cycle'] = max(s.get('seed_cycle', 0), 2000)
|
|
json.dump(s, open(p, 'w'), ensure_ascii=False, indent=2)
|
|
print('State reset — seed_cycle now', s['seed_cycle'])
|
|
"
|
|
|
|
# 3. Start with fixed script
|
|
bash ~/.hermes/scripts/start_scraper_v4.sh
|
|
|
|
# 4. Verify within 2 minutes
|
|
sleep 120 && wc -l ~/.hermes/profiles/nutrition-coach/real_recipes.jsonl
|
|
```
|
|
|
|
---
|
|
|
|
## One-liner state reset (copy-paste)
|
|
|
|
```bash
|
|
python3 -c "
|
|
import json, os
|
|
p = os.path.expanduser('~/.hermes/profiles/nutrition-coach/scraper_state.json')
|
|
s = json.load(open(p))
|
|
s.pop('completed_seeds', None)
|
|
s['seed_cycle'] = max(s.get('seed_cycle',0), 1000)
|
|
json.dump(s, open(p,'w'), ensure_ascii=False, indent=2)
|
|
print('Reset OK — seed_cycle now', s['seed_cycle'])
|
|
"
|
|
```
|
|
|
|
---
|
|
|
|
## Monitoring checklist (what to watch)
|
|
|
|
| Check | Command | Healthy / Unhealthy |
|
|
|-------|---------|---------------------|
|
|
| Recipe count moving | `wc -l real_recipes.jsonl` | Increases between checks |
|
|
| Process alive | `ps auxf \| grep chefkoch_scraper` | chromium + python present |
|
|
| State advancing | `python3 -c "... seed_cycle ..."` | value increases over time |
|
|
| Log not looping | `tail -n 5 scraper_v4.out` | NOT just "Starting: X/Y" only |
|
|
| Disk space | `df -h .` | > 1 GB free |
|