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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: scrape_cli
3
- Version: 1.2.4
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
  [![PyPI version](https://img.shields.io/pypi/v/scrape-cli.svg?label=PyPI%20version)](https://pypi.org/project/scrape-cli/)
16
17
  [![Python Versions](https://img.shields.io/pypi/pyversions/scrape-cli.svg)](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 Integration
234
+ ### JSON Output
234
235
 
235
- You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
236
+ Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
236
237
 
237
238
  ```bash
238
- # Extract and convert to JSON (requires -b for complete HTML)
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 -be "table.data-table td" resources/test.html | xq .
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
- "1",
272
- "John Doe",
273
- "john@example.com",
274
- "2",
275
- "Jane Smith",
276
- "jane@example.com"
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
- **Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
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 Integration
219
+ ### JSON Output
220
220
 
221
- You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
221
+ Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
222
222
 
223
223
  ```bash
224
- # Extract and convert to JSON (requires -b for complete HTML)
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 -be "table.data-table td" resources/test.html | xq .
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
- "1",
258
- "John Doe",
259
- "john@example.com",
260
- "2",
261
- "Jane Smith",
262
- "jane@example.com"
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
- **Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
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.2.4"
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]
@@ -4,7 +4,7 @@ scrape-cli - A command-line tool to extract HTML elements using XPath or CSS3 se
4
4
 
5
5
  from scrape_cli.scrape import main
6
6
 
7
- __version__ = "1.2.4"
7
+ __version__ = "1.3.0"
8
8
  __author__ = "Andrea Borruso"
9
9
  __author_email__ = "aborruso@gmail.com"
10
10
 
@@ -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
- if '-eb' in ' '.join(sys.argv):
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.body and not args.text:
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.2.4
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
  [![PyPI version](https://img.shields.io/pypi/v/scrape-cli.svg?label=PyPI%20version)](https://pypi.org/project/scrape-cli/)
16
17
  [![Python Versions](https://img.shields.io/pypi/pyversions/scrape-cli.svg)](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 Integration
234
+ ### JSON Output
234
235
 
235
- You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
236
+ Use the `-j`/`--json` flag to get structured JSON natively, with no external tools:
236
237
 
237
238
  ```bash
238
- # Extract and convert to JSON (requires -b for complete HTML)
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 -be "table.data-table td" resources/test.html | xq .
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
- "1",
272
- "John Doe",
273
- "john@example.com",
274
- "2",
275
- "Jane Smith",
276
- "jane@example.com"
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
- **Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
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
1
  cssselect
2
2
  lxml
3
3
  requests
4
+ xmltodict
@@ -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