usagov-search 0.2.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- usagov_search-0.2.0/.gitignore +8 -0
- usagov_search-0.2.0/LICENSE +21 -0
- usagov_search-0.2.0/PKG-INFO +487 -0
- usagov_search-0.2.0/README.md +455 -0
- usagov_search-0.2.0/pyproject.toml +56 -0
- usagov_search-0.2.0/src/usagov_search/__init__.py +40 -0
- usagov_search-0.2.0/src/usagov_search/__main__.py +6 -0
- usagov_search-0.2.0/src/usagov_search/agent.py +392 -0
- usagov_search-0.2.0/src/usagov_search/cli.py +223 -0
- usagov_search-0.2.0/src/usagov_search/cli_agent.py +140 -0
- usagov_search-0.2.0/src/usagov_search/client.py +460 -0
- usagov_search-0.2.0/src/usagov_search/models.py +56 -0
- usagov_search-0.2.0/src/usagov_search/parsing.py +69 -0
- usagov_search-0.2.0/src/usagov_search/py.typed +0 -0
- usagov_search-0.2.0/tests/test_agent.py +196 -0
- usagov_search-0.2.0/tests/test_cli.py +88 -0
- usagov_search-0.2.0/tests/test_cli_agent.py +49 -0
- usagov_search-0.2.0/tests/test_html_save.py +21 -0
- usagov_search-0.2.0/tests/test_parsing.py +56 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 usagov-search contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: usagov-search
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Search USA.gov, extract page content, archive HTML, and ask Groq-powered questions
|
|
5
|
+
License: MIT
|
|
6
|
+
License-File: LICENSE
|
|
7
|
+
Keywords: ai,cli,groq,rag,scraper,search,usa.gov,usagov
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Environment :: Console
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
|
+
Classifier: Typing :: Typed
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Requires-Dist: playwright>=1.40
|
|
24
|
+
Provides-Extra: agent
|
|
25
|
+
Requires-Dist: groq>=0.20; extra == 'agent'
|
|
26
|
+
Provides-Extra: dev
|
|
27
|
+
Requires-Dist: build; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=8; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff; extra == 'dev'
|
|
30
|
+
Requires-Dist: twine; extra == 'dev'
|
|
31
|
+
Description-Content-Type: text/markdown
|
|
32
|
+
|
|
33
|
+
# usagov-search
|
|
34
|
+
|
|
35
|
+
Search [USA.gov](https://www.usa.gov/) from Python or the terminal, extract the
|
|
36
|
+
full text of individual result pages, and archive rendered HTML. Supports the
|
|
37
|
+
English and Spanish editions, pagination, JSON/CSV output, and polite rate
|
|
38
|
+
limiting.
|
|
39
|
+
|
|
40
|
+
It also includes an optional AI assistant (`usagov-ask`) that answers questions
|
|
41
|
+
from USA.gov search results or from pages you point it at, with citations.
|
|
42
|
+
|
|
43
|
+
The PyPI distribution is `usagov-search`; the import package is
|
|
44
|
+
`usagov_search`; the commands are `usagov-search` and `usagov-ask`.
|
|
45
|
+
|
|
46
|
+
## Installation
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python -m pip install usagov-search
|
|
50
|
+
python -m playwright install chromium
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Playwright is installed as a dependency, but its Chromium binary is a separate
|
|
54
|
+
download. The second command is required once per machine.
|
|
55
|
+
|
|
56
|
+
## CLI quick start
|
|
57
|
+
|
|
58
|
+
```bash
|
|
59
|
+
usagov-search "passport"
|
|
60
|
+
usagov-search "student loans" --limit 5
|
|
61
|
+
usagov-search "impuestos" --lang es
|
|
62
|
+
|
|
63
|
+
# Also works as a module
|
|
64
|
+
python -m usagov_search "passport" --json
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Library usage
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
from usagov_search import search
|
|
71
|
+
|
|
72
|
+
response = search(
|
|
73
|
+
"passport",
|
|
74
|
+
limit=5,
|
|
75
|
+
extract=[1],
|
|
76
|
+
save_dir="pages",
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
print(response.total) # reported result count
|
|
80
|
+
print(response.returned) # results returned here
|
|
81
|
+
print(response.results[0].title)
|
|
82
|
+
print(response.extracted[0].h1)
|
|
83
|
+
print(response.extracted[0].headings)
|
|
84
|
+
print(response.extracted[0].text) # full page text
|
|
85
|
+
print(response.saved_files) # written HTML paths
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`search()` accepts `extract=[1, 3]`, `extract="1-3"`, or `extract="2,4,7"`.
|
|
89
|
+
Use `save_dir=None` (default) to skip HTML archiving.
|
|
90
|
+
|
|
91
|
+
Lower-level helpers are exported too:
|
|
92
|
+
|
|
93
|
+
```python
|
|
94
|
+
from usagov_search import parse_extract_values, save_html
|
|
95
|
+
|
|
96
|
+
ranks = parse_extract_values(["1-3", "5"]) # [1, 2, 3, 5]
|
|
97
|
+
path = save_html("pages", "custom.html", html)
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
`save_html()` creates the directory and appends `_2`, `_3`, … instead of
|
|
101
|
+
overwriting an existing file.
|
|
102
|
+
|
|
103
|
+
## AI answers (optional, requires your own Groq key)
|
|
104
|
+
|
|
105
|
+
`usagov-search` itself makes no network calls beyond USA.gov. The assistant is an
|
|
106
|
+
optional extra that uses the Groq API. **The package does not ship a key.** Every
|
|
107
|
+
user supplies their own free key from
|
|
108
|
+
[console.groq.com/keys](https://console.groq.com/keys).
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
python -m pip install "usagov-search[agent]"
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Set your key
|
|
115
|
+
|
|
116
|
+
```powershell
|
|
117
|
+
# PowerShell (current session)
|
|
118
|
+
$env:GROQ_API_KEY="gsk_your_key_here"
|
|
119
|
+
|
|
120
|
+
# PowerShell (persist for new terminals)
|
|
121
|
+
setx GROQ_API_KEY "gsk_your_key_here"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
# bash / zsh
|
|
126
|
+
export GROQ_API_KEY="gsk_your_key_here"
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
You can also pass `api_key=` to `USAgovAgent(...)` or let `usagov-ask` prompt for
|
|
130
|
+
it. Prefer the environment variable: keys on the command line end up in shell
|
|
131
|
+
history.
|
|
132
|
+
|
|
133
|
+
### CLI
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Search USA.gov, then answer with citations
|
|
137
|
+
usagov-ask "How do I renew my passport?"
|
|
138
|
+
|
|
139
|
+
# Ask about specific pages instead of searching (USA.gov links only)
|
|
140
|
+
usagov-ask "How long does renewal take?" --url https://www.usa.gov/renew-adult-passport
|
|
141
|
+
|
|
142
|
+
# Several pages at once
|
|
143
|
+
usagov-ask "Compare the options" \
|
|
144
|
+
--url https://www.usa.gov/renew-adult-passport \
|
|
145
|
+
--url https://www.usa.gov/child-passport
|
|
146
|
+
|
|
147
|
+
# Spanish search edition, JSON output
|
|
148
|
+
usagov-ask "¿Cómo renuevo mi pasaporte?" --lang es --json
|
|
149
|
+
|
|
150
|
+
# Interactive session (keeps conversation context)
|
|
151
|
+
usagov-ask
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
If `GROQ_API_KEY` is unset, `usagov-ask` asks for it with a hidden prompt and
|
|
155
|
+
never writes it to disk.
|
|
156
|
+
|
|
157
|
+
| Option | Default | Description |
|
|
158
|
+
|---|---|---|
|
|
159
|
+
| `--url URL` | off | Read this page instead of searching; repeatable |
|
|
160
|
+
| `--lang {en,es}` | `en` | Search the English or Spanish edition |
|
|
161
|
+
| `--model NAME` | `openai/gpt-oss-20b` | Groq model; `USAGOV_MODEL` also works |
|
|
162
|
+
| `--max-sources N` | `4` | Pages to retrieve and read |
|
|
163
|
+
| `--json` | off | Emit `{text, sources, model, warnings}` |
|
|
164
|
+
| `--timeout SEC` | `45` | Per-page browser timeout |
|
|
165
|
+
| `--headed` | off | Show the Chromium window |
|
|
166
|
+
|
|
167
|
+
### Library
|
|
168
|
+
|
|
169
|
+
```python
|
|
170
|
+
import os
|
|
171
|
+
|
|
172
|
+
from usagov_search import USAgovAgent
|
|
173
|
+
|
|
174
|
+
agent = USAgovAgent(api_key=os.environ["GROQ_API_KEY"])
|
|
175
|
+
|
|
176
|
+
answer = agent.ask("What do I need to renew an adult passport?")
|
|
177
|
+
print(answer.text)
|
|
178
|
+
for source in answer.sources:
|
|
179
|
+
print(f"[{source.index}] {source.title} - {source.url}")
|
|
180
|
+
|
|
181
|
+
# Ask about one or more specific pages (must be usa.gov links)
|
|
182
|
+
answer = agent.ask(
|
|
183
|
+
"Can I renew by mail?",
|
|
184
|
+
urls="https://www.usa.gov/renew-adult-passport",
|
|
185
|
+
)
|
|
186
|
+
print(answer.text)
|
|
187
|
+
|
|
188
|
+
agent.reset() # clear conversation history
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
`USAgovAgent` accepts `model`, `max_sources`, `max_source_chars`,
|
|
192
|
+
`max_context_chars`, `temperature`, `max_tokens`, `history_turns`, `timeout`,
|
|
193
|
+
`retries`, `delay`, and `headed`. Pass `client=` to inject your own Groq client
|
|
194
|
+
(tests use this to avoid network calls).
|
|
195
|
+
|
|
196
|
+
How it works:
|
|
197
|
+
|
|
198
|
+
1. Your question is reduced to search keywords and run through the normal
|
|
199
|
+
`usagov-search` retrieval path (or your `urls` are fetched directly).
|
|
200
|
+
2. Page text is trimmed to a character budget and numbered as `[1]`, `[2]`, …
|
|
201
|
+
3. One Groq chat completion receives the numbered sources plus the question and
|
|
202
|
+
is instructed to answer only from them and cite `[n]`.
|
|
203
|
+
4. The answer and its source list are returned together.
|
|
204
|
+
|
|
205
|
+
What leaves your machine: the question and the trimmed page excerpts are sent to
|
|
206
|
+
Groq. Page fetching stays between you and USA.gov. Do not paste secrets into
|
|
207
|
+
questions.
|
|
208
|
+
|
|
209
|
+
Limitations: answers reflect the page text at request time, not a live policy
|
|
210
|
+
database; USA.gov can change deadlines and fees. The model is told to refuse
|
|
211
|
+
when the sources do not contain the answer, but verify official details on the
|
|
212
|
+
linked page. Extracted text is plain text, so links and tables lose structure.
|
|
213
|
+
|
|
214
|
+
## Why Playwright?
|
|
215
|
+
|
|
216
|
+
USA.gov search is served by `search.usa.gov`, which sits behind an AWS WAF
|
|
217
|
+
JavaScript challenge. Plain HTTP clients (`requests`, `curl`, `urllib`) receive
|
|
218
|
+
HTTP 202 with an empty body. The package drives Chromium through
|
|
219
|
+
[Playwright](https://playwright.dev/python/) so the challenge can complete, then
|
|
220
|
+
parses the rendered DOM.
|
|
221
|
+
|
|
222
|
+
## CLI examples
|
|
223
|
+
|
|
224
|
+
```bash
|
|
225
|
+
# English search, default 20 results
|
|
226
|
+
usagov-search "passport"
|
|
227
|
+
|
|
228
|
+
# Limit results
|
|
229
|
+
usagov-search "passport" --limit 5
|
|
230
|
+
|
|
231
|
+
# Spanish edition
|
|
232
|
+
usagov-search "pasaporte" --lang es
|
|
233
|
+
|
|
234
|
+
# Start from page 2
|
|
235
|
+
usagov-search "taxes" --page 2 --limit 3
|
|
236
|
+
|
|
237
|
+
# Fetch across pages (20 results per page)
|
|
238
|
+
usagov-search "passport" --limit 45 --delay 1
|
|
239
|
+
|
|
240
|
+
# All available pages, capped at 5 pages
|
|
241
|
+
usagov-search "passport" --limit 0 --max-pages 5
|
|
242
|
+
|
|
243
|
+
# Machine-readable output
|
|
244
|
+
usagov-search "student loans" --json
|
|
245
|
+
usagov-search "student loans" --csv > results.csv
|
|
246
|
+
|
|
247
|
+
# Extract full contents of specific result pages (1-based ranks)
|
|
248
|
+
usagov-search "passport" --limit 5 --extract 1
|
|
249
|
+
usagov-search "passport" --limit 5 --extract 1-3
|
|
250
|
+
usagov-search "passport" --limit 10 --extract 2,4,7
|
|
251
|
+
|
|
252
|
+
# Full body text in table output (default limit: 4000 characters)
|
|
253
|
+
usagov-search "passport" --extract 1 --extract-chars 0
|
|
254
|
+
|
|
255
|
+
# Save search pages and extracted pages as rendered HTML
|
|
256
|
+
usagov-search "passport" --extract 2 --save-html pages
|
|
257
|
+
|
|
258
|
+
# Show the browser window (useful if the WAF challenge stalls)
|
|
259
|
+
usagov-search "passport" --headed
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
`--extract` refers to ranks in the returned result list, so referenced ranks
|
|
263
|
+
must exist within `--limit` (default 20). Put `QUERY` before `--save-html`;
|
|
264
|
+
the option takes an optional directory name.
|
|
265
|
+
|
|
266
|
+
## Options
|
|
267
|
+
|
|
268
|
+
| Option | Default | Description |
|
|
269
|
+
|---|---|---|
|
|
270
|
+
| `--lang {en,es}` | `en` | Search the English or Spanish edition |
|
|
271
|
+
| `--affiliate ID` | auto | Override the `search.usa.gov` affiliate ID |
|
|
272
|
+
| `--page N` | `1` | First results page (1-based) |
|
|
273
|
+
| `--limit N` | `20` | Maximum results; `0` means all pages up to `--max-pages` |
|
|
274
|
+
| `--max-pages N` | `10` | Safety cap on pages fetched |
|
|
275
|
+
| `--timeout SEC` | `45` | Per-page navigation/result timeout |
|
|
276
|
+
| `--retries N` | `2` | Retries when the WAF stalls a request |
|
|
277
|
+
| `--delay SEC` | `1.0` | Delay between page requests |
|
|
278
|
+
| `--json` | off | Emit JSON |
|
|
279
|
+
| `--csv` | off | Emit CSV |
|
|
280
|
+
| `--snippet-width N` | `160` | Truncate titles/snippets in table output |
|
|
281
|
+
| `--extract SPEC` | off | Extract page contents for ranks, e.g. `1`, `1,3`, `1-3`; repeatable |
|
|
282
|
+
| `--extract-chars N` | `4000` | Truncate extracted text in table output; `0` means all |
|
|
283
|
+
| `--save-html [DIR]` | off | Save rendered HTML to `DIR` (default `saved_pages`) |
|
|
284
|
+
| `--headed` | off | Show the Chromium window |
|
|
285
|
+
|
|
286
|
+
`--json` and `--csv` are mutually exclusive.
|
|
287
|
+
|
|
288
|
+
## Output
|
|
289
|
+
|
|
290
|
+
### Table
|
|
291
|
+
|
|
292
|
+
```text
|
|
293
|
+
Query: passport (en) - 31 reported, 3 returned
|
|
294
|
+
|
|
295
|
+
1. Renew an adult passport | USAGov
|
|
296
|
+
https://www.usa.gov/renew-adult-passport
|
|
297
|
+
Find out if you can renew your passport or if you have to get a new one...
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
### Extracted content
|
|
301
|
+
|
|
302
|
+
`--extract` prints the result list, then a detail block per requested page:
|
|
303
|
+
final URL, metadata, heading outline, an excerpt of the body text (up to
|
|
304
|
+
`--extract-chars`), and the HTML path when `--save-html` is used. JSON and CSV
|
|
305
|
+
always contain the full extracted text.
|
|
306
|
+
|
|
307
|
+
```text
|
|
308
|
+
Extracted result 1: Renew an adult passport | USAGov
|
|
309
|
+
URL: https://www.usa.gov/renew-adult-passport
|
|
310
|
+
H1: Renew an adult passport
|
|
311
|
+
Description: Find out if you can renew your passport or if you have to get a new one...
|
|
312
|
+
Last updated: March 23, 2026
|
|
313
|
+
Headings (15):
|
|
314
|
+
H1: Renew an adult passport
|
|
315
|
+
H2: Warning
|
|
316
|
+
H2: Determine if you can renew your passport
|
|
317
|
+
...
|
|
318
|
+
|
|
319
|
+
Renew an adult passport
|
|
320
|
+
|
|
321
|
+
Find out if you can renew your passport or need a new one...
|
|
322
|
+
|
|
323
|
+
LAST UPDATED: March 23, 2026
|
|
324
|
+
|
|
325
|
+
HTML: pages\page_01_renew-adult-passport.html
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### JSON
|
|
329
|
+
|
|
330
|
+
```json
|
|
331
|
+
{
|
|
332
|
+
"query": "student loans",
|
|
333
|
+
"lang": "en",
|
|
334
|
+
"affiliate": "usagov_en_internal",
|
|
335
|
+
"total": 10,
|
|
336
|
+
"returned": 2,
|
|
337
|
+
"results": [
|
|
338
|
+
{
|
|
339
|
+
"rank": 1,
|
|
340
|
+
"title": "Resolve student loan payment problems | USAGov",
|
|
341
|
+
"url": "https://www.usa.gov/student-loan-problems",
|
|
342
|
+
"display_url": "www.usa.gov/student-loan-problems",
|
|
343
|
+
"snippet": "Contact your loan servicer if you are struggling to repay..."
|
|
344
|
+
}
|
|
345
|
+
],
|
|
346
|
+
"extracted": [
|
|
347
|
+
{
|
|
348
|
+
"rank": 1,
|
|
349
|
+
"requested_url": "https://www.usa.gov/student-loan-problems",
|
|
350
|
+
"final_url": "https://www.usa.gov/student-loan-problems",
|
|
351
|
+
"title": "Resolve student loan payment problems | USAGov",
|
|
352
|
+
"h1": "Resolve student loan payment problems",
|
|
353
|
+
"description": "Contact your loan servicer if you are struggling...",
|
|
354
|
+
"canonical": "https://www.usa.gov/student-loan-problems",
|
|
355
|
+
"last_updated": "August 15, 2026",
|
|
356
|
+
"headings": [{"level": "h1", "text": "Resolve student loan payment problems"}],
|
|
357
|
+
"text": "Full page text...",
|
|
358
|
+
"html_file": null
|
|
359
|
+
}
|
|
360
|
+
],
|
|
361
|
+
"saved_files": [],
|
|
362
|
+
"warnings": []
|
|
363
|
+
}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
`extracted` is empty unless `--extract` is used; `saved_files` lists every file
|
|
367
|
+
written by `--save-html`.
|
|
368
|
+
|
|
369
|
+
### CSV
|
|
370
|
+
|
|
371
|
+
```csv
|
|
372
|
+
rank,title,url,display_url,snippet,content,html_file
|
|
373
|
+
1,Taxes | USAGov,https://www.usa.gov/taxes,www.usa.gov/taxes,"income tax. Check the status...",,
|
|
374
|
+
2,Renew an adult passport | USAGov,https://www.usa.gov/renew-adult-passport,www.usa.gov/renew-adult-passport,"Find out if you can renew...","Renew an adult passport...",pages\page_02_renew-adult-passport.html
|
|
375
|
+
```
|
|
376
|
+
|
|
377
|
+
Extracted rows repeat the search result fields and add `content` (the body
|
|
378
|
+
text) and `html_file` (blank unless the page was saved).
|
|
379
|
+
|
|
380
|
+
## How it works
|
|
381
|
+
|
|
382
|
+
1. Builds a request to:
|
|
383
|
+
|
|
384
|
+
```text
|
|
385
|
+
https://search.usa.gov/search?affiliate=usagov_en_internal&query=...&page=N
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
The Spanish affiliate is `usagov_es_internal`.
|
|
389
|
+
|
|
390
|
+
2. Loads the page in Chromium and waits for the WAF challenge to finish.
|
|
391
|
+
3. Extracts results from the rendered DOM:
|
|
392
|
+
|
|
393
|
+
| Data | Selector |
|
|
394
|
+
|---|---|
|
|
395
|
+
| Result cards | `#results a.no-underline` |
|
|
396
|
+
| Title | `.result-title` |
|
|
397
|
+
| Snippet | `.result-desc p` |
|
|
398
|
+
| Display URL | `.result-url-text` |
|
|
399
|
+
| Total count | `.results-count` |
|
|
400
|
+
| Next page | `.usa-pagination__next-page` |
|
|
401
|
+
|
|
402
|
+
4. Follows the "Next" link until `limit` is reached or `max_pages` is hit.
|
|
403
|
+
5. De-duplicates by URL and assigns sequential ranks.
|
|
404
|
+
6. For each extracted rank, opens the result page and clones its main content
|
|
405
|
+
(`<main id="main-content">`, falling back to `<main>` or `<body>`). It removes
|
|
406
|
+
chrome and scripts (`nav`, `header`, `footer`, `aside`, `form`, `button`,
|
|
407
|
+
`svg`, `iframe`, `[aria-hidden="true"]`), then reads `innerText` to produce
|
|
408
|
+
readable text, alongside the title, meta description, canonical URL,
|
|
409
|
+
last-updated value, and the H1–H4 outline.
|
|
410
|
+
7. With `save_dir` / `--save-html`, writes the fully rendered DOM for each
|
|
411
|
+
search page and extracted page to the target directory.
|
|
412
|
+
|
|
413
|
+
Search-result parsing lives in `client.extract_search_page()`; page extraction
|
|
414
|
+
lives in `client.extract_page()`. If USA.gov changes its markup, update the
|
|
415
|
+
selectors there.
|
|
416
|
+
|
|
417
|
+
## Notes and limitations
|
|
418
|
+
|
|
419
|
+
- **Rate limits:** `search.usa.gov` rate-limits bursts. Run one instance at a
|
|
420
|
+
time and keep `--delay` at 1 second or higher when paging or extracting.
|
|
421
|
+
- **WAF stalls:** If the challenge does not complete, retry after a minute,
|
|
422
|
+
increase `--timeout`, or use `--headed`.
|
|
423
|
+
- **Result order** can vary slightly between runs.
|
|
424
|
+
- **Snippets** include the site's own duplicated fragments
|
|
425
|
+
(e.g. `Learn...Learn how to renew...`).
|
|
426
|
+
- **Rank** is relative to the returned set, so `--page 2` starts again at 1.
|
|
427
|
+
- **Extraction is text-only:** links, tables, and layout are flattened into
|
|
428
|
+
plain text. Use HTML saving when you need the original structure.
|
|
429
|
+
- **One request per extracted page.** Larger extraction sets take longer; keep
|
|
430
|
+
`--delay` at 1 second or higher.
|
|
431
|
+
- **Saved HTML is a rendered snapshot.** Assets and links may still be relative
|
|
432
|
+
to `www.usa.gov`, so opening the file offline can look unstyled.
|
|
433
|
+
- This is an unofficial convenience client. Respect the site's terms and do not
|
|
434
|
+
hammer it.
|
|
435
|
+
|
|
436
|
+
## Development
|
|
437
|
+
|
|
438
|
+
```bash
|
|
439
|
+
git clone <your-repo>
|
|
440
|
+
cd <repo-checkout>
|
|
441
|
+
python -m pip install -e ".[dev,agent]"
|
|
442
|
+
python -m playwright install chromium
|
|
443
|
+
pytest
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
The package uses a src layout:
|
|
447
|
+
|
|
448
|
+
```text
|
|
449
|
+
src/usagov_search/
|
|
450
|
+
├── __init__.py # public API and version
|
|
451
|
+
├── __main__.py # python -m usagov_search
|
|
452
|
+
├── agent.py # USAgovAgent: retrieval + Groq answer with citations
|
|
453
|
+
├── cli.py # argument parsing and output formatting
|
|
454
|
+
├── cli_agent.py # usagov-ask
|
|
455
|
+
├── client.py # search, fetch_page, extraction, HTML saving
|
|
456
|
+
├── models.py # Result, ExtractedPage, SearchResponse
|
|
457
|
+
├── parsing.py # count/rank parsing and text helpers
|
|
458
|
+
└── py.typed
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Tests under `tests/` are offline. They cover parsing, HTML saving, CLI output,
|
|
462
|
+
and the agent (using a fake Groq client); they do not launch a browser or call
|
|
463
|
+
Groq.
|
|
464
|
+
|
|
465
|
+
## Publishing
|
|
466
|
+
|
|
467
|
+
```bash
|
|
468
|
+
python -m pip install --upgrade build twine
|
|
469
|
+
python -m build
|
|
470
|
+
python -m twine check dist/*
|
|
471
|
+
|
|
472
|
+
# Verify on TestPyPI first
|
|
473
|
+
python -m twine upload --repository testpypi dist/*
|
|
474
|
+
python -m pip install --index-url https://test.pypi.org/simple/ usagov-search
|
|
475
|
+
|
|
476
|
+
# Production
|
|
477
|
+
python -m twine upload dist/*
|
|
478
|
+
```
|
|
479
|
+
|
|
480
|
+
Use a PyPI API token with username `__token__` and an environment variable;
|
|
481
|
+
never commit the token. Bump `__version__` in `src/usagov_search/__init__.py`
|
|
482
|
+
before each release.
|
|
483
|
+
|
|
484
|
+
## License
|
|
485
|
+
|
|
486
|
+
MIT. USA.gov content itself is subject to its own terms; this package only
|
|
487
|
+
provides a client for the public search interface.
|