scrape-cli 1.2.4__tar.gz → 1.3.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.
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/PKG-INFO +16 -13
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/README.md +14 -12
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/pyproject.toml +3 -2
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli/__init__.py +1 -1
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli/scrape.py +28 -3
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/PKG-INFO +16 -13
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/requires.txt +1 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/tests/test_scrape.py +46 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/SOURCES.txt +0 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/dependency_links.txt +0 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/entry_points.txt +0 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/scrape_cli.egg-info/top_level.txt +0 -0
- {scrape_cli-1.2.4 → scrape_cli-1.3.0}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrape_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
|
|
5
5
|
Author-email: Andrea Borruso <aborruso@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/aborruso/scrape-cli
|
|
@@ -11,6 +11,7 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
Requires-Dist: cssselect
|
|
12
12
|
Requires-Dist: lxml
|
|
13
13
|
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: xmltodict
|
|
14
15
|
|
|
15
16
|
[](https://pypi.org/project/scrape-cli/)
|
|
16
17
|
[](https://pypi.org/project/scrape-cli/)
|
|
@@ -230,13 +231,12 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
230
231
|
|
|
231
232
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
232
233
|
|
|
233
|
-
### JSON Output
|
|
234
|
+
### JSON Output
|
|
234
235
|
|
|
235
|
-
|
|
236
|
+
Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
|
|
236
237
|
|
|
237
238
|
```bash
|
|
238
|
-
|
|
239
|
-
scrape -be "a.external-link" resources/test.html | xq .
|
|
239
|
+
scrape -je "a.external-link" resources/test.html
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
Output:
|
|
@@ -258,7 +258,7 @@ Output:
|
|
|
258
258
|
Table extraction example:
|
|
259
259
|
|
|
260
260
|
```bash
|
|
261
|
-
scrape -
|
|
261
|
+
scrape -je "table.data-table td" resources/test.html
|
|
262
262
|
```
|
|
263
263
|
|
|
264
264
|
Output:
|
|
@@ -268,19 +268,21 @@ Output:
|
|
|
268
268
|
"html": {
|
|
269
269
|
"body": {
|
|
270
270
|
"td": [
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
271
|
+
"Italy",
|
|
272
|
+
"Rome",
|
|
273
|
+
"59",
|
|
274
|
+
"France",
|
|
275
|
+
"Paris",
|
|
276
|
+
"68"
|
|
277
277
|
]
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
`-j` automatically wraps the result in `<html>/<body>` before conversion, so you don't need to pass `-b`. The output is the same you would get from `scrape -be ... | xq .` (the underlying converter is `xmltodict`, the same library used by `xq`).
|
|
284
|
+
|
|
285
|
+
`-j` is mutually exclusive with `-t`, `-x` (`--check-existence`) and `-a` (`--argument`).
|
|
284
286
|
|
|
285
287
|
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
286
288
|
|
|
@@ -288,6 +290,7 @@ Some notes on the commands:
|
|
|
288
290
|
|
|
289
291
|
- `-e` to set the query
|
|
290
292
|
- `-b` to add `<html>`, `<head>` and `<body>` tags to the HTML output
|
|
293
|
+
- `-j` to output structured JSON (built-in)
|
|
291
294
|
- `-t` to extract only text content (useful for LLMs and text processing)
|
|
292
295
|
|
|
293
296
|
|
|
@@ -216,13 +216,12 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
216
216
|
|
|
217
217
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
218
218
|
|
|
219
|
-
### JSON Output
|
|
219
|
+
### JSON Output
|
|
220
220
|
|
|
221
|
-
|
|
221
|
+
Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
|
|
222
222
|
|
|
223
223
|
```bash
|
|
224
|
-
|
|
225
|
-
scrape -be "a.external-link" resources/test.html | xq .
|
|
224
|
+
scrape -je "a.external-link" resources/test.html
|
|
226
225
|
```
|
|
227
226
|
|
|
228
227
|
Output:
|
|
@@ -244,7 +243,7 @@ Output:
|
|
|
244
243
|
Table extraction example:
|
|
245
244
|
|
|
246
245
|
```bash
|
|
247
|
-
scrape -
|
|
246
|
+
scrape -je "table.data-table td" resources/test.html
|
|
248
247
|
```
|
|
249
248
|
|
|
250
249
|
Output:
|
|
@@ -254,19 +253,21 @@ Output:
|
|
|
254
253
|
"html": {
|
|
255
254
|
"body": {
|
|
256
255
|
"td": [
|
|
257
|
-
"
|
|
258
|
-
"
|
|
259
|
-
"
|
|
260
|
-
"
|
|
261
|
-
"
|
|
262
|
-
"
|
|
256
|
+
"Italy",
|
|
257
|
+
"Rome",
|
|
258
|
+
"59",
|
|
259
|
+
"France",
|
|
260
|
+
"Paris",
|
|
261
|
+
"68"
|
|
263
262
|
]
|
|
264
263
|
}
|
|
265
264
|
}
|
|
266
265
|
}
|
|
267
266
|
```
|
|
268
267
|
|
|
269
|
-
|
|
268
|
+
`-j` automatically wraps the result in `<html>/<body>` before conversion, so you don't need to pass `-b`. The output is the same you would get from `scrape -be ... | xq .` (the underlying converter is `xmltodict`, the same library used by `xq`).
|
|
269
|
+
|
|
270
|
+
`-j` is mutually exclusive with `-t`, `-x` (`--check-existence`) and `-a` (`--argument`).
|
|
270
271
|
|
|
271
272
|
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
272
273
|
|
|
@@ -274,6 +275,7 @@ Some notes on the commands:
|
|
|
274
275
|
|
|
275
276
|
- `-e` to set the query
|
|
276
277
|
- `-b` to add `<html>`, `<head>` and `<body>` tags to the HTML output
|
|
278
|
+
- `-j` to output structured JSON (built-in)
|
|
277
279
|
- `-t` to extract only text content (useful for LLMs and text processing)
|
|
278
280
|
|
|
279
281
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scrape_cli"
|
|
7
|
-
version = "1.
|
|
7
|
+
version = "1.3.0"
|
|
8
8
|
description = "It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
authors = [
|
|
@@ -18,7 +18,8 @@ requires-python = ">=3.8"
|
|
|
18
18
|
dependencies = [
|
|
19
19
|
"cssselect",
|
|
20
20
|
"lxml",
|
|
21
|
-
"requests"
|
|
21
|
+
"requests",
|
|
22
|
+
"xmltodict"
|
|
22
23
|
]
|
|
23
24
|
|
|
24
25
|
[project.urls]
|
|
@@ -13,8 +13,10 @@
|
|
|
13
13
|
import os
|
|
14
14
|
import sys
|
|
15
15
|
import re
|
|
16
|
+
import json
|
|
16
17
|
import argparse
|
|
17
18
|
import requests
|
|
19
|
+
import xmltodict
|
|
18
20
|
from lxml import etree
|
|
19
21
|
from cssselect import GenericTranslator
|
|
20
22
|
|
|
@@ -109,15 +111,20 @@ examples:
|
|
|
109
111
|
cat file.html | scrape -e "//h1" read from stdin
|
|
110
112
|
scrape -e "//h1" -x file.html check existence (exit 0/1)
|
|
111
113
|
scrape -be "//article" file.html wrap output in <html><body>
|
|
114
|
+
scrape -je "//article" file.html output as JSON (built-in)
|
|
112
115
|
'''
|
|
113
116
|
)
|
|
114
117
|
|
|
115
118
|
parser.add_argument('--version', action='version', version=f'%(prog)s {__version__}')
|
|
116
119
|
|
|
117
|
-
# Check for incorrect argument order (-eb instead of -be)
|
|
118
|
-
|
|
120
|
+
# Check for incorrect argument order (-eb instead of -be, -ej instead of -je)
|
|
121
|
+
joined_argv = ' '.join(sys.argv)
|
|
122
|
+
if '-eb' in joined_argv:
|
|
119
123
|
print("Error: use -be not -eb.\n scrape -be \"//article\" file.html", file=sys.stderr)
|
|
120
124
|
sys.exit(1)
|
|
125
|
+
if '-ej' in joined_argv:
|
|
126
|
+
print("Error: use -je not -ej.\n scrape -je \"//article\" file.html", file=sys.stderr)
|
|
127
|
+
sys.exit(1)
|
|
121
128
|
# Defines the HTML input argument (can be a file, URL or stdin)
|
|
122
129
|
parser.add_argument('html', nargs='?', type=str, default='',
|
|
123
130
|
help="HTML input (file, URL or stdin, default: stdin)", metavar="HTML")
|
|
@@ -130,6 +137,9 @@ examples:
|
|
|
130
137
|
# Option to extract only text content
|
|
131
138
|
parser.add_argument('-t', '--text', action='store_true', default=False,
|
|
132
139
|
help="Extract only text content (useful for LLMs)")
|
|
140
|
+
# Option to output structured JSON (built-in, no external xq needed)
|
|
141
|
+
parser.add_argument('-j', '--json', action='store_true', default=False,
|
|
142
|
+
help="Output result as structured JSON")
|
|
133
143
|
# Allows to specify one or more XPath or CSS3 selector expressions
|
|
134
144
|
parser.add_argument('-e', '--expression', default=[], action='append',
|
|
135
145
|
help="XPath query or CSS3 selector")
|
|
@@ -143,6 +153,17 @@ examples:
|
|
|
143
153
|
help="Custom User-Agent string for HTTP requests")
|
|
144
154
|
args = parser.parse_args()
|
|
145
155
|
|
|
156
|
+
# JSON flag is mutually exclusive with -t, -x, -a
|
|
157
|
+
if args.json and args.text:
|
|
158
|
+
print("Error: --json and --text are mutually exclusive.", file=sys.stderr)
|
|
159
|
+
sys.exit(1)
|
|
160
|
+
if args.json and args.check_existence:
|
|
161
|
+
print("Error: --json and --check-existence are mutually exclusive.", file=sys.stderr)
|
|
162
|
+
sys.exit(1)
|
|
163
|
+
if args.json and args.argument:
|
|
164
|
+
print("Error: --json cannot be combined with -a/--argument.", file=sys.stderr)
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
|
|
146
167
|
# Check that at least one expression is provided by the user (unless using -t option)
|
|
147
168
|
if not args.expression and not args.text:
|
|
148
169
|
print(
|
|
@@ -281,7 +302,11 @@ examples:
|
|
|
281
302
|
results = [final_text] if final_text else []
|
|
282
303
|
|
|
283
304
|
# Output handling
|
|
284
|
-
if args.
|
|
305
|
+
if args.json:
|
|
306
|
+
wrapped = "<!DOCTYPE html>\n<html>\n<body>\n" + "".join(r + "\n" for r in results) + "</body>\n</html>\n"
|
|
307
|
+
parsed = xmltodict.parse(wrapped)
|
|
308
|
+
sys.stdout.write(json.dumps(parsed, indent=2, ensure_ascii=False) + "\n")
|
|
309
|
+
elif args.body and not args.text:
|
|
285
310
|
sys.stdout.write("<!DOCTYPE html>\n<html>\n<body>\n")
|
|
286
311
|
for result in results:
|
|
287
312
|
sys.stdout.write(result + "\n")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrape_cli
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.3.0
|
|
4
4
|
Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
|
|
5
5
|
Author-email: Andrea Borruso <aborruso@gmail.com>
|
|
6
6
|
Project-URL: Homepage, https://github.com/aborruso/scrape-cli
|
|
@@ -11,6 +11,7 @@ Description-Content-Type: text/markdown
|
|
|
11
11
|
Requires-Dist: cssselect
|
|
12
12
|
Requires-Dist: lxml
|
|
13
13
|
Requires-Dist: requests
|
|
14
|
+
Requires-Dist: xmltodict
|
|
14
15
|
|
|
15
16
|
[](https://pypi.org/project/scrape-cli/)
|
|
16
17
|
[](https://pypi.org/project/scrape-cli/)
|
|
@@ -230,13 +231,12 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
230
231
|
|
|
231
232
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
232
233
|
|
|
233
|
-
### JSON Output
|
|
234
|
+
### JSON Output
|
|
234
235
|
|
|
235
|
-
|
|
236
|
+
Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
|
|
236
237
|
|
|
237
238
|
```bash
|
|
238
|
-
|
|
239
|
-
scrape -be "a.external-link" resources/test.html | xq .
|
|
239
|
+
scrape -je "a.external-link" resources/test.html
|
|
240
240
|
```
|
|
241
241
|
|
|
242
242
|
Output:
|
|
@@ -258,7 +258,7 @@ Output:
|
|
|
258
258
|
Table extraction example:
|
|
259
259
|
|
|
260
260
|
```bash
|
|
261
|
-
scrape -
|
|
261
|
+
scrape -je "table.data-table td" resources/test.html
|
|
262
262
|
```
|
|
263
263
|
|
|
264
264
|
Output:
|
|
@@ -268,19 +268,21 @@ Output:
|
|
|
268
268
|
"html": {
|
|
269
269
|
"body": {
|
|
270
270
|
"td": [
|
|
271
|
-
"
|
|
272
|
-
"
|
|
273
|
-
"
|
|
274
|
-
"
|
|
275
|
-
"
|
|
276
|
-
"
|
|
271
|
+
"Italy",
|
|
272
|
+
"Rome",
|
|
273
|
+
"59",
|
|
274
|
+
"France",
|
|
275
|
+
"Paris",
|
|
276
|
+
"68"
|
|
277
277
|
]
|
|
278
278
|
}
|
|
279
279
|
}
|
|
280
280
|
}
|
|
281
281
|
```
|
|
282
282
|
|
|
283
|
-
|
|
283
|
+
`-j` automatically wraps the result in `<html>/<body>` before conversion, so you don't need to pass `-b`. The output is the same you would get from `scrape -be ... | xq .` (the underlying converter is `xmltodict`, the same library used by `xq`).
|
|
284
|
+
|
|
285
|
+
`-j` is mutually exclusive with `-t`, `-x` (`--check-existence`) and `-a` (`--argument`).
|
|
284
286
|
|
|
285
287
|
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
286
288
|
|
|
@@ -288,6 +290,7 @@ Some notes on the commands:
|
|
|
288
290
|
|
|
289
291
|
- `-e` to set the query
|
|
290
292
|
- `-b` to add `<html>`, `<head>` and `<body>` tags to the HTML output
|
|
293
|
+
- `-j` to output structured JSON (built-in)
|
|
291
294
|
- `-t` to extract only text content (useful for LLMs and text processing)
|
|
292
295
|
|
|
293
296
|
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import json
|
|
1
2
|
import subprocess
|
|
2
3
|
import sys
|
|
3
4
|
import threading
|
|
@@ -189,6 +190,51 @@ def test_invalid_css_selector_fails_conversion():
|
|
|
189
190
|
assert "Error converting CSS selector to XPath" in result.stderr
|
|
190
191
|
|
|
191
192
|
|
|
193
|
+
def test_json_flag_produces_valid_json_for_single_element():
|
|
194
|
+
result = run_scrape(str(TEST_HTML), "-je", "a.external-link")
|
|
195
|
+
|
|
196
|
+
assert result.returncode == 0
|
|
197
|
+
payload = json.loads(result.stdout)
|
|
198
|
+
a = payload["html"]["body"]["a"]
|
|
199
|
+
assert a["@href"] == "https://example.com"
|
|
200
|
+
assert a["@class"] == "external-link"
|
|
201
|
+
assert a["#text"] == "Example Link"
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def test_json_flag_produces_array_for_multiple_elements():
|
|
205
|
+
result = run_scrape(str(TEST_HTML), "-je", "table.data-table td")
|
|
206
|
+
|
|
207
|
+
assert result.returncode == 0
|
|
208
|
+
payload = json.loads(result.stdout)
|
|
209
|
+
tds = payload["html"]["body"]["td"]
|
|
210
|
+
assert isinstance(tds, list)
|
|
211
|
+
assert "Italy" in tds
|
|
212
|
+
assert "Rome" in tds
|
|
213
|
+
|
|
214
|
+
|
|
215
|
+
def test_json_flag_works_without_b_flag():
|
|
216
|
+
only_j = run_scrape(str(TEST_HTML), "-j", "-e", "a.external-link")
|
|
217
|
+
with_bj = run_scrape(str(TEST_HTML), "-bj", "-e", "a.external-link")
|
|
218
|
+
|
|
219
|
+
assert only_j.returncode == 0
|
|
220
|
+
assert with_bj.returncode == 0
|
|
221
|
+
assert only_j.stdout == with_bj.stdout
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def test_json_flag_conflicts_with_text_check_existence_argument():
|
|
225
|
+
jt = run_scrape(str(TEST_HTML), "-j", "-t")
|
|
226
|
+
assert jt.returncode == 1
|
|
227
|
+
assert "mutually exclusive" in jt.stderr
|
|
228
|
+
|
|
229
|
+
jx = run_scrape(str(TEST_HTML), "-j", "-e", "//h1", "-x")
|
|
230
|
+
assert jx.returncode == 1
|
|
231
|
+
assert "mutually exclusive" in jx.stderr
|
|
232
|
+
|
|
233
|
+
ja = run_scrape(str(TEST_HTML), "-j", "-e", "//a", "-a", "href")
|
|
234
|
+
assert ja.returncode == 1
|
|
235
|
+
assert "cannot be combined" in ja.stderr
|
|
236
|
+
|
|
237
|
+
|
|
192
238
|
def test_url_input_downloads_and_extracts_text():
|
|
193
239
|
html_bytes = TEST_HTML.read_bytes()
|
|
194
240
|
server, thread = run_test_server(html_bytes)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|