Skip to content

Commit c9e45bc

Browse files
committed
Fix HN favorites: strip Unix epoch from date, add retry + browser headers, persist cache between runs
1 parent d8fa122 commit c9e45bc

2 files changed

Lines changed: 42 additions & 17 deletions

File tree

.github/workflows/build-blog.yml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ jobs:
3737
restore-keys: |
3838
youtube-cache-
3939
40+
- name: Restore HN Favorites cache
41+
uses: actions/cache@v4
42+
with:
43+
path: _cache/hn_favorites.json
44+
key: hn-favorites-cache-${{ github.run_id }}
45+
restore-keys: |
46+
hn-favorites-cache-
47+
4048
- name: Detect GitHub Pages domain
4149
id: pages-domain
4250
env:

blog/ingestors/hackernews_favorites.py

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
"""
1111

1212
import re
13+
import time
1314
import urllib.parse
1415
from html.parser import HTMLParser
1516

@@ -19,6 +20,19 @@
1920

2021
_HN_BASE = "https://news.ycombinator.com"
2122
_MAX_ITEMS = 30
23+
_REQUEST_HEADERS = {
24+
"User-Agent": (
25+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
26+
"AppleWebKit/537.36 (KHTML, like Gecko) "
27+
"Chrome/120.0.0.0 Safari/537.36"
28+
),
29+
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
30+
"Accept-Language": "en-US,en;q=0.5",
31+
"Accept-Encoding": "gzip, deflate, br",
32+
"DNT": "1",
33+
"Connection": "keep-alive",
34+
"Upgrade-Insecure-Requests": "1",
35+
}
2236

2337

2438
# ---------------------------------------------------------------------------
@@ -105,7 +119,8 @@ def handle_starttag(self, tag: str, attrs: list[tuple]) -> None:
105119
if tag == "span" and "age" in classes:
106120
date = a.get("title", "")
107121
if date:
108-
# Ensure a timezone suffix for RFC 3339 compliance
122+
# HN title format: "YYYY-MM-DDTHH:MM:SS UNIX_EPOCH" — take only the ISO part
123+
date = date.split()[0]
109124
if "T" in date and not (
110125
date.endswith("Z") or "+" in date[-7:] or "-" in date[-7:]
111126
):
@@ -164,24 +179,26 @@ def close(self) -> None:
164179

165180
def _fetch_page(url: str, warnings: list[str]) -> tuple[str, str | None]:
166181
"""
167-
Fetch one page of the HN favorites listing.
182+
Fetch one page of the HN favorites listing with simple retry on 429.
168183
Returns ``(html_text, next_page_url_or_None)``.
169184
"""
170-
try:
171-
resp = requests.get(url, timeout=30, headers={
172-
"User-Agent": (
173-
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
174-
"AppleWebKit/537.36 (KHTML, like Gecko) "
175-
"Chrome/120.0.0.0 Safari/537.36"
176-
)
177-
})
178-
resp.raise_for_status()
179-
except requests.RequestException as exc:
180-
msg = f"Warning: HN favorites fetch error ({url}): {exc}"
181-
print(f" {msg}")
182-
warnings.append(msg)
183-
return "", None
184-
return resp.text, None
185+
for attempt in range(3):
186+
try:
187+
resp = requests.get(url, timeout=30, headers=_REQUEST_HEADERS)
188+
if resp.status_code == 429 and attempt < 2:
189+
time.sleep(3 * (attempt + 1)) # 3s then 6s
190+
continue
191+
resp.raise_for_status()
192+
except requests.RequestException as exc:
193+
if attempt < 2:
194+
time.sleep(3 * (attempt + 1))
195+
continue
196+
msg = f"Warning: HN favorites fetch error ({url}): {exc}"
197+
print(f" {msg}")
198+
warnings.append(msg)
199+
return "", None
200+
return resp.text, None
201+
return "", None
185202

186203

187204
def _scrape_favorites(username: str, max_items: int, warnings: list[str]) -> list[dict]:

0 commit comments

Comments
 (0)