Paginate the full standards list (general + professional domains). NIFDC ``qxqwk.do`` lists only 228 general-domain standards by default. To fetch ALL 2100+ standards (including the professional/specialized domain), we use the ``istiaojian`` query parameter -- any non-empty value trigge
(max_pages: Optional[int] = None)
| 162 | |
| 163 | |
| 164 | def _stable_id(number: str) -> str: |
| 165 | base = number.lower().strip().replace(" ", "-").replace("/", "-") |
| 166 | return f"nmpa-{re.sub(r'[^a-z0-9.-]+', '-', base)}" |
| 167 | |
| 168 | |
| 169 | def fetch_all(max_pages: Optional[int] = None) -> tuple[list[dict], dict]: |
| 170 | """Paginate the full standards list (general + professional domains). |
| 171 | |
| 172 | NIFDC ``qxqwk.do`` lists only 228 general-domain standards by default. To |
| 173 | fetch ALL 2100+ standards (including the professional/specialized domain), |
| 174 | we use the ``istiaojian`` query parameter -- any non-empty value triggers |
| 175 | the full-catalog pagination. Each page returns up to ~300 rows (from |
| 176 | multiple ``<table>`` elements). We paginate with ``index=N`` in the URL. |
| 177 | |
| 178 | Stops when two consecutive pages yield zero new (de-duped) rows. |
| 179 | """ |
| 180 | session = _make_session() |
| 181 | # Warm up session cookies |
| 182 | _request_with_retry(session, "GET", LIST_URL) |
| 183 | all_rows: list[dict] = [] |
| 184 | seen_keys: set[str] = set() |
| 185 | page = 1 |
| 186 | consecutive_empty = 0 |
| 187 | while True: |
| 188 | if max_pages and page > max_pages: |
| 189 | break |
| 190 | url = f"{LIST_ACTION_URL}&istiaojian=all&index={page}" |
| 191 | r = _request_with_retry(session, "GET", url) |
| 192 | if r is None: |
| 193 | print(f"[error] page {page} failed all retries; stopping.") |
| 194 | break |
| 195 | if r.status_code != 200: |
| 196 | print(f"[error] page {page} HTTP {r.status_code}; stopping.") |
| 197 | break |
| 198 | rows, _info = _parse_listing_page(r.text) |
| 199 | new_count = 0 |
| 200 | for row in rows: |
| 201 | key = row["number"] |
| 202 | if key in seen_keys: |
| 203 | continue |
| 204 | seen_keys.add(key) |
| 205 | row["status"] = _normalize_status(row.get("status_zh", "")) |
| 206 | row["id"] = _stable_id(row["number"]) |
| 207 | row["source_url"] = LIST_URL |
| 208 | all_rows.append(row) |
| 209 | new_count += 1 |
| 210 | print(f"[info] page {page}: parsed {len(rows)} (new {new_count}; total {len(all_rows)})", flush=True) |
| 211 | if new_count == 0: |
| 212 | consecutive_empty += 1 |
| 213 | if consecutive_empty >= 2: |
| 214 | break |
| 215 | else: |
| 216 | consecutive_empty = 0 |
| 217 | if not rows: |
| 218 | break |
| 219 | page += 1 |
| 220 | time.sleep(SLEEP_BETWEEN) |
| 221 | return all_rows, { |
no test coverage detected