scrape-cli 1.2.0__tar.gz → 1.2.1__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.0 → scrape_cli-1.2.1}/PKG-INFO +66 -9
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/README.md +64 -2
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/pyproject.toml +2 -2
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli/__init__.py +1 -1
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli/scrape.py +6 -6
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/PKG-INFO +66 -9
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/SOURCES.txt +2 -2
- scrape_cli-1.2.1/tests/test_scrape.py +205 -0
- scrape_cli-1.2.0/setup.py +0 -37
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/dependency_links.txt +0 -0
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/entry_points.txt +0 -0
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/requires.txt +0 -0
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/scrape_cli.egg-info/top_level.txt +0 -0
- {scrape_cli-1.2.0 → scrape_cli-1.2.1}/setup.cfg +0 -0
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrape_cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
|
|
5
|
-
Home-page: https://github.com/aborruso/scrape-cli
|
|
6
|
-
Author: Andrea Borruso
|
|
7
5
|
Author-email: Andrea Borruso <aborruso@gmail.com>
|
|
8
6
|
Project-URL: Homepage, https://github.com/aborruso/scrape-cli
|
|
9
7
|
Classifier: Programming Language :: Python :: 3
|
|
10
8
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.8
|
|
12
10
|
Description-Content-Type: text/markdown
|
|
13
11
|
Requires-Dist: cssselect
|
|
14
12
|
Requires-Dist: lxml
|
|
15
13
|
Requires-Dist: requests
|
|
16
|
-
Dynamic: author
|
|
17
|
-
Dynamic: home-page
|
|
18
|
-
Dynamic: requires-python
|
|
19
14
|
|
|
20
15
|
[](https://pypi.org/project/scrape-cli/)
|
|
21
16
|
[](https://pypi.org/project/scrape-cli/)
|
|
@@ -52,7 +47,7 @@ uv tool install scrape-cli
|
|
|
52
47
|
uv pip install scrape-cli
|
|
53
48
|
|
|
54
49
|
# Or run temporarily without installing
|
|
55
|
-
uvx scrape-cli --help
|
|
50
|
+
uvx --from scrape-cli scrape --help
|
|
56
51
|
```
|
|
57
52
|
|
|
58
53
|
### Using pip
|
|
@@ -80,7 +75,15 @@ pip install -e .
|
|
|
80
75
|
|
|
81
76
|
### Using the Test HTML File
|
|
82
77
|
|
|
83
|
-
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
78
|
+
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
79
|
+
|
|
80
|
+
**Note**: You can also test directly from the URL without cloning the repository:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
scrape -e "h1" https://raw.githubusercontent.com/aborruso/scrape-cli/refs/heads/master/resources/test.html
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Here are some examples:
|
|
84
87
|
|
|
85
88
|
1. Extract all table data:
|
|
86
89
|
|
|
@@ -226,6 +229,60 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
226
229
|
|
|
227
230
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
228
231
|
|
|
232
|
+
### JSON Output Integration
|
|
233
|
+
|
|
234
|
+
You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Extract and convert to JSON (requires -b for complete HTML)
|
|
238
|
+
scrape -be "a.external-link" resources/test.html | xq .
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Output:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"html": {
|
|
246
|
+
"body": {
|
|
247
|
+
"a": {
|
|
248
|
+
"@href": "https://example.com",
|
|
249
|
+
"@class": "external-link",
|
|
250
|
+
"#text": "Example Link"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Table extraction example:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
scrape -be "table.data-table td" resources/test.html | xq .
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Output:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"html": {
|
|
268
|
+
"body": {
|
|
269
|
+
"td": [
|
|
270
|
+
"1",
|
|
271
|
+
"John Doe",
|
|
272
|
+
"john@example.com",
|
|
273
|
+
"2",
|
|
274
|
+
"Jane Smith",
|
|
275
|
+
"jane@example.com"
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
|
|
283
|
+
|
|
284
|
+
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
285
|
+
|
|
229
286
|
Some notes on the commands:
|
|
230
287
|
|
|
231
288
|
- `-e` to set the query
|
|
@@ -33,7 +33,7 @@ uv tool install scrape-cli
|
|
|
33
33
|
uv pip install scrape-cli
|
|
34
34
|
|
|
35
35
|
# Or run temporarily without installing
|
|
36
|
-
uvx scrape-cli --help
|
|
36
|
+
uvx --from scrape-cli scrape --help
|
|
37
37
|
```
|
|
38
38
|
|
|
39
39
|
### Using pip
|
|
@@ -61,7 +61,15 @@ pip install -e .
|
|
|
61
61
|
|
|
62
62
|
### Using the Test HTML File
|
|
63
63
|
|
|
64
|
-
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
64
|
+
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
65
|
+
|
|
66
|
+
**Note**: You can also test directly from the URL without cloning the repository:
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
scrape -e "h1" https://raw.githubusercontent.com/aborruso/scrape-cli/refs/heads/master/resources/test.html
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Here are some examples:
|
|
65
73
|
|
|
66
74
|
1. Extract all table data:
|
|
67
75
|
|
|
@@ -207,6 +215,60 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
207
215
|
|
|
208
216
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
209
217
|
|
|
218
|
+
### JSON Output Integration
|
|
219
|
+
|
|
220
|
+
You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
# Extract and convert to JSON (requires -b for complete HTML)
|
|
224
|
+
scrape -be "a.external-link" resources/test.html | xq .
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Output:
|
|
228
|
+
|
|
229
|
+
```json
|
|
230
|
+
{
|
|
231
|
+
"html": {
|
|
232
|
+
"body": {
|
|
233
|
+
"a": {
|
|
234
|
+
"@href": "https://example.com",
|
|
235
|
+
"@class": "external-link",
|
|
236
|
+
"#text": "Example Link"
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
Table extraction example:
|
|
244
|
+
|
|
245
|
+
```bash
|
|
246
|
+
scrape -be "table.data-table td" resources/test.html | xq .
|
|
247
|
+
```
|
|
248
|
+
|
|
249
|
+
Output:
|
|
250
|
+
|
|
251
|
+
```json
|
|
252
|
+
{
|
|
253
|
+
"html": {
|
|
254
|
+
"body": {
|
|
255
|
+
"td": [
|
|
256
|
+
"1",
|
|
257
|
+
"John Doe",
|
|
258
|
+
"john@example.com",
|
|
259
|
+
"2",
|
|
260
|
+
"Jane Smith",
|
|
261
|
+
"jane@example.com"
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
**Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
|
|
269
|
+
|
|
270
|
+
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
271
|
+
|
|
210
272
|
Some notes on the commands:
|
|
211
273
|
|
|
212
274
|
- `-e` to set the query
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "scrape_cli"
|
|
7
|
-
version = "1.2.
|
|
7
|
+
version = "1.2.1"
|
|
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 = [
|
|
@@ -14,7 +14,7 @@ classifiers = [
|
|
|
14
14
|
"Programming Language :: Python :: 3",
|
|
15
15
|
"Operating System :: OS Independent",
|
|
16
16
|
]
|
|
17
|
-
requires-python = ">=3.
|
|
17
|
+
requires-python = ">=3.8"
|
|
18
18
|
dependencies = [
|
|
19
19
|
"cssselect",
|
|
20
20
|
"lxml",
|
|
@@ -63,13 +63,13 @@ def is_xpath(expression):
|
|
|
63
63
|
- Expressions wrapped in parentheses that contain XPath syntax
|
|
64
64
|
"""
|
|
65
65
|
expr = expression.strip()
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
# Direct XPath patterns
|
|
68
68
|
if expr.startswith('/') or expr.startswith('//'):
|
|
69
69
|
return True
|
|
70
70
|
if '::' in expr:
|
|
71
71
|
return True
|
|
72
|
-
|
|
72
|
+
|
|
73
73
|
# Handle expressions wrapped in parentheses
|
|
74
74
|
if expr.startswith('(') and expr.endswith(')'):
|
|
75
75
|
# Remove outer parentheses and check inner content
|
|
@@ -78,7 +78,7 @@ def is_xpath(expression):
|
|
|
78
78
|
return True
|
|
79
79
|
if '::' in inner_expr:
|
|
80
80
|
return True
|
|
81
|
-
|
|
81
|
+
|
|
82
82
|
# Additional XPath indicators
|
|
83
83
|
# Check for XPath-specific patterns that CSS doesn't have
|
|
84
84
|
if '//' in expr or expr.startswith('/'):
|
|
@@ -91,7 +91,7 @@ def is_xpath(expression):
|
|
|
91
91
|
return True
|
|
92
92
|
if re.search(r'\b(ancestor|descendant|following|preceding|parent|child)::', expr): # XPath axes
|
|
93
93
|
return True
|
|
94
|
-
|
|
94
|
+
|
|
95
95
|
return False
|
|
96
96
|
|
|
97
97
|
def main():
|
|
@@ -142,7 +142,7 @@ def main():
|
|
|
142
142
|
if args.html.startswith('http://') or args.html.startswith('https://'):
|
|
143
143
|
# If the input is a URL, download the HTML content
|
|
144
144
|
try:
|
|
145
|
-
response = requests.get(args.html)
|
|
145
|
+
response = requests.get(args.html, timeout=30)
|
|
146
146
|
response.raise_for_status()
|
|
147
147
|
inp = response.content
|
|
148
148
|
except requests.RequestException as e:
|
|
@@ -189,7 +189,7 @@ def main():
|
|
|
189
189
|
meta = re.search(r'<meta[^>]+charset=["\']?([\w-]+)', head)
|
|
190
190
|
if meta:
|
|
191
191
|
return meta.group(1)
|
|
192
|
-
except:
|
|
192
|
+
except Exception:
|
|
193
193
|
pass
|
|
194
194
|
return None
|
|
195
195
|
|
|
@@ -1,21 +1,16 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: scrape_cli
|
|
3
|
-
Version: 1.2.
|
|
3
|
+
Version: 1.2.1
|
|
4
4
|
Summary: It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.
|
|
5
|
-
Home-page: https://github.com/aborruso/scrape-cli
|
|
6
|
-
Author: Andrea Borruso
|
|
7
5
|
Author-email: Andrea Borruso <aborruso@gmail.com>
|
|
8
6
|
Project-URL: Homepage, https://github.com/aborruso/scrape-cli
|
|
9
7
|
Classifier: Programming Language :: Python :: 3
|
|
10
8
|
Classifier: Operating System :: OS Independent
|
|
11
|
-
Requires-Python: >=3.
|
|
9
|
+
Requires-Python: >=3.8
|
|
12
10
|
Description-Content-Type: text/markdown
|
|
13
11
|
Requires-Dist: cssselect
|
|
14
12
|
Requires-Dist: lxml
|
|
15
13
|
Requires-Dist: requests
|
|
16
|
-
Dynamic: author
|
|
17
|
-
Dynamic: home-page
|
|
18
|
-
Dynamic: requires-python
|
|
19
14
|
|
|
20
15
|
[](https://pypi.org/project/scrape-cli/)
|
|
21
16
|
[](https://pypi.org/project/scrape-cli/)
|
|
@@ -52,7 +47,7 @@ uv tool install scrape-cli
|
|
|
52
47
|
uv pip install scrape-cli
|
|
53
48
|
|
|
54
49
|
# Or run temporarily without installing
|
|
55
|
-
uvx scrape-cli --help
|
|
50
|
+
uvx --from scrape-cli scrape --help
|
|
56
51
|
```
|
|
57
52
|
|
|
58
53
|
### Using pip
|
|
@@ -80,7 +75,15 @@ pip install -e .
|
|
|
80
75
|
|
|
81
76
|
### Using the Test HTML File
|
|
82
77
|
|
|
83
|
-
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
78
|
+
In the `resources` directory you'll find a `test.html` file that you can use to test various scraping scenarios.
|
|
79
|
+
|
|
80
|
+
**Note**: You can also test directly from the URL without cloning the repository:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
scrape -e "h1" https://raw.githubusercontent.com/aborruso/scrape-cli/refs/heads/master/resources/test.html
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Here are some examples:
|
|
84
87
|
|
|
85
88
|
1. Extract all table data:
|
|
86
89
|
|
|
@@ -226,6 +229,60 @@ scrape -te 'h1, h2, h3' resources/test.html
|
|
|
226
229
|
|
|
227
230
|
The `-t` option automatically excludes text from `<script>` and `<style>` tags and cleans up whitespace for better readability.
|
|
228
231
|
|
|
232
|
+
### JSON Output Integration
|
|
233
|
+
|
|
234
|
+
You can integrate scrape-cli with [xq](https://github.com/kislyuk/yq) (part of yq) to convert HTML output to structured JSON:
|
|
235
|
+
|
|
236
|
+
```bash
|
|
237
|
+
# Extract and convert to JSON (requires -b for complete HTML)
|
|
238
|
+
scrape -be "a.external-link" resources/test.html | xq .
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Output:
|
|
242
|
+
|
|
243
|
+
```json
|
|
244
|
+
{
|
|
245
|
+
"html": {
|
|
246
|
+
"body": {
|
|
247
|
+
"a": {
|
|
248
|
+
"@href": "https://example.com",
|
|
249
|
+
"@class": "external-link",
|
|
250
|
+
"#text": "Example Link"
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Table extraction example:
|
|
258
|
+
|
|
259
|
+
```bash
|
|
260
|
+
scrape -be "table.data-table td" resources/test.html | xq .
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
Output:
|
|
264
|
+
|
|
265
|
+
```json
|
|
266
|
+
{
|
|
267
|
+
"html": {
|
|
268
|
+
"body": {
|
|
269
|
+
"td": [
|
|
270
|
+
"1",
|
|
271
|
+
"John Doe",
|
|
272
|
+
"john@example.com",
|
|
273
|
+
"2",
|
|
274
|
+
"Jane Smith",
|
|
275
|
+
"jane@example.com"
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
```
|
|
281
|
+
|
|
282
|
+
**Note**: The `-b` flag is mandatory to produce valid HTML with `<html>`, `<head>` and `<body>` tags.
|
|
283
|
+
|
|
284
|
+
Useful for JSON-based pipelines, APIs, databases, and processing with jq/DuckDB.
|
|
285
|
+
|
|
229
286
|
Some notes on the commands:
|
|
230
287
|
|
|
231
288
|
- `-e` to set the query
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
README.md
|
|
2
2
|
pyproject.toml
|
|
3
|
-
setup.py
|
|
4
3
|
scrape_cli/__init__.py
|
|
5
4
|
scrape_cli/scrape.py
|
|
6
5
|
scrape_cli.egg-info/PKG-INFO
|
|
@@ -8,4 +7,5 @@ scrape_cli.egg-info/SOURCES.txt
|
|
|
8
7
|
scrape_cli.egg-info/dependency_links.txt
|
|
9
8
|
scrape_cli.egg-info/entry_points.txt
|
|
10
9
|
scrape_cli.egg-info/requires.txt
|
|
11
|
-
scrape_cli.egg-info/top_level.txt
|
|
10
|
+
scrape_cli.egg-info/top_level.txt
|
|
11
|
+
tests/test_scrape.py
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import sys
|
|
3
|
+
import threading
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from http.server import BaseHTTPRequestHandler, HTTPServer
|
|
6
|
+
|
|
7
|
+
ROOT = Path(__file__).resolve().parents[1]
|
|
8
|
+
TEST_HTML = ROOT / "resources" / "test.html"
|
|
9
|
+
sys.path.insert(0, str(ROOT))
|
|
10
|
+
|
|
11
|
+
from scrape_cli.scrape import is_xpath
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def run_scrape(*args, input_data=None):
|
|
15
|
+
cmd = [sys.executable, "-m", "scrape_cli.scrape", *args]
|
|
16
|
+
return subprocess.run(
|
|
17
|
+
cmd,
|
|
18
|
+
capture_output=True,
|
|
19
|
+
text=True,
|
|
20
|
+
cwd=ROOT,
|
|
21
|
+
input=input_data,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def run_test_server(html_bytes):
|
|
26
|
+
class Handler(BaseHTTPRequestHandler):
|
|
27
|
+
def do_GET(self):
|
|
28
|
+
self.send_response(200)
|
|
29
|
+
self.send_header("Content-Type", "text/html; charset=utf-8")
|
|
30
|
+
self.end_headers()
|
|
31
|
+
self.wfile.write(html_bytes)
|
|
32
|
+
|
|
33
|
+
def log_message(self, format, *args):
|
|
34
|
+
return
|
|
35
|
+
|
|
36
|
+
server = HTTPServer(("127.0.0.1", 0), Handler)
|
|
37
|
+
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
|
38
|
+
thread.start()
|
|
39
|
+
return server, thread
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_is_xpath_true_patterns():
|
|
43
|
+
candidates = [
|
|
44
|
+
"//div",
|
|
45
|
+
"/html/body/div",
|
|
46
|
+
"(//div)[1]",
|
|
47
|
+
"//a/@href",
|
|
48
|
+
"//li[2]",
|
|
49
|
+
"ancestor::div",
|
|
50
|
+
"descendant::span",
|
|
51
|
+
"//p/text()",
|
|
52
|
+
]
|
|
53
|
+
|
|
54
|
+
for expression in candidates:
|
|
55
|
+
assert is_xpath(expression) is True
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def test_is_xpath_false_css_patterns():
|
|
59
|
+
candidates = [
|
|
60
|
+
"div.content > a.link",
|
|
61
|
+
"a[href*='/about']",
|
|
62
|
+
"input[type='email']",
|
|
63
|
+
"ul.items-list li:first-child",
|
|
64
|
+
"div.class1.class2",
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
for expression in candidates:
|
|
68
|
+
assert is_xpath(expression) is False
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
def test_xpath_parentheses_extracts_first_match():
|
|
72
|
+
result = run_scrape(str(TEST_HTML), "-e", "(//ul[@class='items-list']/li)[1]", "-t")
|
|
73
|
+
|
|
74
|
+
assert result.returncode == 0
|
|
75
|
+
assert result.stdout.strip() == "First item"
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def test_css_attribute_selector_is_not_misclassified_as_xpath():
|
|
79
|
+
result = run_scrape(str(TEST_HTML), "-e", "a[href*='/about']", "-t")
|
|
80
|
+
|
|
81
|
+
assert result.returncode == 0
|
|
82
|
+
assert result.stdout.strip() == "About Page"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_check_existence_true_and_false():
|
|
86
|
+
found = run_scrape(str(TEST_HTML), "-e", "//h1", "--check-existence")
|
|
87
|
+
missing = run_scrape(str(TEST_HTML), "-e", "//this-node-does-not-exist", "--check-existence")
|
|
88
|
+
|
|
89
|
+
assert found.returncode == 0
|
|
90
|
+
assert missing.returncode == 1
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
def test_encoding_meta_charset_iso_8859_1(tmp_path):
|
|
94
|
+
html = """<!doctype html>
|
|
95
|
+
<html>
|
|
96
|
+
<head><meta charset=\"iso-8859-1\"></head>
|
|
97
|
+
<body><p>Perch\xe9</p></body>
|
|
98
|
+
</html>
|
|
99
|
+
""".encode("iso-8859-1")
|
|
100
|
+
sample = tmp_path / "latin1.html"
|
|
101
|
+
sample.write_bytes(html)
|
|
102
|
+
|
|
103
|
+
result = run_scrape(str(sample), "-e", "//p/text()", "-t")
|
|
104
|
+
|
|
105
|
+
assert result.returncode == 0
|
|
106
|
+
assert result.stdout.strip() == "Perché"
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
def test_argument_extracts_attribute_value():
|
|
110
|
+
result = run_scrape(str(TEST_HTML), "-e", "//a[@class='external-link']", "-a", "href")
|
|
111
|
+
|
|
112
|
+
assert result.returncode == 0
|
|
113
|
+
assert result.stdout.strip() == "https://example.com"
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
def test_body_flag_wraps_output_in_html_body():
|
|
117
|
+
result = run_scrape(str(TEST_HTML), "-e", "//h1", "-b")
|
|
118
|
+
|
|
119
|
+
assert result.returncode == 0
|
|
120
|
+
assert result.stdout.startswith("<!DOCTYPE html>\n<html>\n<body>\n")
|
|
121
|
+
assert result.stdout.strip().endswith("</body>\n</html>")
|
|
122
|
+
assert "<h1 id=\"main-title\">Welcome to the Test Page</h1>" in result.stdout
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def test_text_flag_without_expression_extracts_body_and_skips_script():
|
|
126
|
+
result = run_scrape(str(TEST_HTML), "-t")
|
|
127
|
+
|
|
128
|
+
assert result.returncode == 0
|
|
129
|
+
assert "Welcome to the Test Page" in result.stdout
|
|
130
|
+
assert "document.getElementById('dynamic-content')" not in result.stdout
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def test_short_check_existence_flag_x():
|
|
134
|
+
found = run_scrape(str(TEST_HTML), "-e", "//table", "-x")
|
|
135
|
+
missing = run_scrape(str(TEST_HTML), "-e", "//definitely-not-here", "-x")
|
|
136
|
+
|
|
137
|
+
assert found.returncode == 0
|
|
138
|
+
assert missing.returncode == 1
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
def test_rawinput_parses_xml_without_html_parser():
|
|
142
|
+
xml_data = "<root><item>one</item><item>two</item></root>"
|
|
143
|
+
result = run_scrape("-e", "//item[2]/text()", "-r", input_data=xml_data)
|
|
144
|
+
|
|
145
|
+
assert result.returncode == 0
|
|
146
|
+
assert result.stdout.strip() == "two"
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def test_stdin_input_works_when_no_html_argument():
|
|
150
|
+
html_data = "<html><body><p>stdin-ok</p></body></html>"
|
|
151
|
+
result = run_scrape("-e", "//p/text()", "-t", input_data=html_data)
|
|
152
|
+
|
|
153
|
+
assert result.returncode == 0
|
|
154
|
+
assert result.stdout.strip() == "stdin-ok"
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_empty_stdin_returns_error():
|
|
158
|
+
result = run_scrape("-e", "//p", input_data="")
|
|
159
|
+
|
|
160
|
+
assert result.returncode == 1
|
|
161
|
+
assert "Error: No input received from stdin" in result.stdout
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
def test_missing_file_returns_error():
|
|
165
|
+
result = run_scrape("resources/this-file-does-not-exist.html", "-e", "//p")
|
|
166
|
+
|
|
167
|
+
assert result.returncode == 1
|
|
168
|
+
assert "was not found" in result.stdout
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
def test_missing_expression_without_text_returns_error():
|
|
172
|
+
result = run_scrape(str(TEST_HTML))
|
|
173
|
+
|
|
174
|
+
assert result.returncode == 1
|
|
175
|
+
assert "you must provide at least one XPath query or CSS3 selector" in result.stderr
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
def test_incorrect_eb_order_exits_with_specific_message():
|
|
179
|
+
result = run_scrape("-eb")
|
|
180
|
+
|
|
181
|
+
assert result.returncode == 1
|
|
182
|
+
assert "Please use -be instead of -eb." in result.stderr
|
|
183
|
+
|
|
184
|
+
|
|
185
|
+
def test_invalid_css_selector_fails_conversion():
|
|
186
|
+
result = run_scrape(str(TEST_HTML), "-e", "div[")
|
|
187
|
+
|
|
188
|
+
assert result.returncode == 1
|
|
189
|
+
assert "Error converting CSS selector to XPath" in result.stdout
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_url_input_downloads_and_extracts_text():
|
|
193
|
+
html_bytes = TEST_HTML.read_bytes()
|
|
194
|
+
server, thread = run_test_server(html_bytes)
|
|
195
|
+
|
|
196
|
+
try:
|
|
197
|
+
url = f"http://127.0.0.1:{server.server_address[1]}"
|
|
198
|
+
result = run_scrape(url, "-e", "//h1/text()", "-t")
|
|
199
|
+
finally:
|
|
200
|
+
server.shutdown()
|
|
201
|
+
server.server_close()
|
|
202
|
+
thread.join(timeout=2)
|
|
203
|
+
|
|
204
|
+
assert result.returncode == 0
|
|
205
|
+
assert result.stdout.strip() == "Welcome to the Test Page"
|
scrape_cli-1.2.0/setup.py
DELETED
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
# setup.py
|
|
2
|
-
from setuptools import setup
|
|
3
|
-
from pathlib import Path
|
|
4
|
-
|
|
5
|
-
# Leggi il README
|
|
6
|
-
this_directory = Path(__file__).parent
|
|
7
|
-
long_description = (this_directory / "README.md").read_text(encoding="utf-8")
|
|
8
|
-
|
|
9
|
-
setup(
|
|
10
|
-
name="scrape_cli",
|
|
11
|
-
version="1.1.9",
|
|
12
|
-
description="It's a command-line tool to extract HTML elements using an XPath query or CSS3 selector.",
|
|
13
|
-
long_description=long_description,
|
|
14
|
-
long_description_content_type="text/markdown",
|
|
15
|
-
author="Andrea Borruso",
|
|
16
|
-
author_email="aborruso@gmail.com",
|
|
17
|
-
url="https://github.com/aborruso/scrape-cli",
|
|
18
|
-
license="MIT",
|
|
19
|
-
packages=["scrape_cli"],
|
|
20
|
-
package_dir={"scrape_cli": "scrape_cli"},
|
|
21
|
-
entry_points={
|
|
22
|
-
'console_scripts': [
|
|
23
|
-
'scrape=scrape_cli.scrape:main',
|
|
24
|
-
],
|
|
25
|
-
},
|
|
26
|
-
install_requires=[
|
|
27
|
-
"cssselect",
|
|
28
|
-
"lxml",
|
|
29
|
-
"requests"
|
|
30
|
-
],
|
|
31
|
-
classifiers=[
|
|
32
|
-
"Programming Language :: Python :: 3",
|
|
33
|
-
"License :: OSI Approved :: MIT License",
|
|
34
|
-
"Operating System :: OS Independent",
|
|
35
|
-
],
|
|
36
|
-
python_requires='>=3.6',
|
|
37
|
-
)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|