av-ld-json-extractor 0.1.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.
- av_ld_json_extractor-0.1.0/PKG-INFO +82 -0
- av_ld_json_extractor-0.1.0/README.md +72 -0
- av_ld_json_extractor-0.1.0/pyproject.toml +108 -0
- av_ld_json_extractor-0.1.0/src/av_ld_json_extractor/__init__.py +1 -0
- av_ld_json_extractor-0.1.0/src/av_ld_json_extractor/_ld_json.py +44 -0
- av_ld_json_extractor-0.1.0/src/av_ld_json_extractor/py.typed +0 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: av-ld-json-extractor
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Extract ld+json from HTML
|
|
5
|
+
Author: Avazart
|
|
6
|
+
Author-email: Avazart <avazart.fl@gmail.com>
|
|
7
|
+
Requires-Dist: selectolax>=0.4.11
|
|
8
|
+
Requires-Python: >=3.12
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
|
|
11
|
+
# av-ld_json_extractor
|
|
12
|
+
|
|
13
|
+
A lightweight Python library designed to extract ld+json from `<script>` tags within HTML documents
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
Using [`uv`](https://github.com/astral-sh/uv):
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
uv add av-ld_json_extractor
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Quick Start
|
|
24
|
+
|
|
25
|
+
#### 1. Extract JSON-LD Data from HTML
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from selectolax.lexbor import LexborHTMLParser
|
|
29
|
+
from av_ld_json_extractor import ld_json_iter
|
|
30
|
+
|
|
31
|
+
html_content = """
|
|
32
|
+
<!DOCTYPE html>
|
|
33
|
+
<html>
|
|
34
|
+
<head>
|
|
35
|
+
<script type="application/ld+json">
|
|
36
|
+
{"@context": "[https://schema.org](https://schema.org)", "@type": "Organization", "name": "Example Corp"}
|
|
37
|
+
</script>
|
|
38
|
+
<script type="application/ld+json">
|
|
39
|
+
{"@type": "Product", "name": "Gaming Laptop", "price": 1200}
|
|
40
|
+
</script>
|
|
41
|
+
</head>
|
|
42
|
+
</html>
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
parser = LexborHTMLParser(html_content)
|
|
46
|
+
|
|
47
|
+
for data in ld_json_iter(parser):
|
|
48
|
+
print(data)
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
```
|
|
52
|
+
# Output:
|
|
53
|
+
# {'@context': '[https://schema.org](https://schema.org)', '@type': 'Organization', 'name': 'Example Corp'}
|
|
54
|
+
# {'@type': 'Product', 'name': 'Gaming Laptop', 'price': 1200}
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
#### 2. Find Specific @type (Objects & Lists)
|
|
58
|
+
|
|
59
|
+
```python
|
|
60
|
+
from selectolax.lexbor import LexborHTMLParser
|
|
61
|
+
from av_ld_json_extractor import find_ld_json
|
|
62
|
+
|
|
63
|
+
html_content = """
|
|
64
|
+
<script type="application/ld+json">
|
|
65
|
+
[
|
|
66
|
+
{"@type": "BreadcrumbList", "itemListElement": []},
|
|
67
|
+
{"@type": "Product", "name": "Wireless Mouse", "price": 25}
|
|
68
|
+
]
|
|
69
|
+
</script>
|
|
70
|
+
"""
|
|
71
|
+
|
|
72
|
+
parser = LexborHTMLParser(html_content)
|
|
73
|
+
|
|
74
|
+
# Automatically searches inside both single JSON objects and array lists
|
|
75
|
+
product = find_ld_json("Product", parser)
|
|
76
|
+
print(product)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
# Output:
|
|
81
|
+
# {'@type': 'Product', 'name': 'Wireless Mouse', 'price': 25}
|
|
82
|
+
```
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# av-ld_json_extractor
|
|
2
|
+
|
|
3
|
+
A lightweight Python library designed to extract ld+json from `<script>` tags within HTML documents
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Using [`uv`](https://github.com/astral-sh/uv):
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
uv add av-ld_json_extractor
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Quick Start
|
|
14
|
+
|
|
15
|
+
#### 1. Extract JSON-LD Data from HTML
|
|
16
|
+
|
|
17
|
+
```python
|
|
18
|
+
from selectolax.lexbor import LexborHTMLParser
|
|
19
|
+
from av_ld_json_extractor import ld_json_iter
|
|
20
|
+
|
|
21
|
+
html_content = """
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html>
|
|
24
|
+
<head>
|
|
25
|
+
<script type="application/ld+json">
|
|
26
|
+
{"@context": "[https://schema.org](https://schema.org)", "@type": "Organization", "name": "Example Corp"}
|
|
27
|
+
</script>
|
|
28
|
+
<script type="application/ld+json">
|
|
29
|
+
{"@type": "Product", "name": "Gaming Laptop", "price": 1200}
|
|
30
|
+
</script>
|
|
31
|
+
</head>
|
|
32
|
+
</html>
|
|
33
|
+
"""
|
|
34
|
+
|
|
35
|
+
parser = LexborHTMLParser(html_content)
|
|
36
|
+
|
|
37
|
+
for data in ld_json_iter(parser):
|
|
38
|
+
print(data)
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
# Output:
|
|
43
|
+
# {'@context': '[https://schema.org](https://schema.org)', '@type': 'Organization', 'name': 'Example Corp'}
|
|
44
|
+
# {'@type': 'Product', 'name': 'Gaming Laptop', 'price': 1200}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
#### 2. Find Specific @type (Objects & Lists)
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
from selectolax.lexbor import LexborHTMLParser
|
|
51
|
+
from av_ld_json_extractor import find_ld_json
|
|
52
|
+
|
|
53
|
+
html_content = """
|
|
54
|
+
<script type="application/ld+json">
|
|
55
|
+
[
|
|
56
|
+
{"@type": "BreadcrumbList", "itemListElement": []},
|
|
57
|
+
{"@type": "Product", "name": "Wireless Mouse", "price": 25}
|
|
58
|
+
]
|
|
59
|
+
</script>
|
|
60
|
+
"""
|
|
61
|
+
|
|
62
|
+
parser = LexborHTMLParser(html_content)
|
|
63
|
+
|
|
64
|
+
# Automatically searches inside both single JSON objects and array lists
|
|
65
|
+
product = find_ld_json("Product", parser)
|
|
66
|
+
print(product)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
# Output:
|
|
71
|
+
# {'@type': 'Product', 'name': 'Wireless Mouse', 'price': 25}
|
|
72
|
+
```
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "av-ld-json-extractor"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "Extract ld+json from HTML"
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
authors = [
|
|
7
|
+
{ name = "Avazart", email = "avazart.fl@gmail.com" }
|
|
8
|
+
]
|
|
9
|
+
requires-python = ">=3.12"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"selectolax>=0.4.11",
|
|
12
|
+
]
|
|
13
|
+
|
|
14
|
+
[build-system]
|
|
15
|
+
requires = ["uv_build>=0.8.21,<0.9.0"]
|
|
16
|
+
build-backend = "uv_build"
|
|
17
|
+
|
|
18
|
+
[dependency-groups]
|
|
19
|
+
dev = [
|
|
20
|
+
"mypy>=2.3.1",
|
|
21
|
+
"pre-commit>=4.6.2",
|
|
22
|
+
"pytest>=9.1.1",
|
|
23
|
+
"ruff>=0.16.6",
|
|
24
|
+
"vulture>=2.16",
|
|
25
|
+
]
|
|
26
|
+
[tool.ruff]
|
|
27
|
+
exclude = [
|
|
28
|
+
".git",
|
|
29
|
+
"__pycache__",
|
|
30
|
+
".pytest_cache",
|
|
31
|
+
".mypy_cache",
|
|
32
|
+
".ruff_cache",
|
|
33
|
+
"venv",
|
|
34
|
+
".venv",
|
|
35
|
+
".env",
|
|
36
|
+
".idea",
|
|
37
|
+
".vscode",
|
|
38
|
+
]
|
|
39
|
+
line-length = 79
|
|
40
|
+
target-version = "py311"
|
|
41
|
+
|
|
42
|
+
[tool.ruff.format]
|
|
43
|
+
quote-style = "double"
|
|
44
|
+
skip-magic-trailing-comma = false
|
|
45
|
+
|
|
46
|
+
[tool.ruff.lint]
|
|
47
|
+
select = [
|
|
48
|
+
# pycodestyle
|
|
49
|
+
#"E",
|
|
50
|
+
# Pyflakes
|
|
51
|
+
"F",
|
|
52
|
+
# pyupgrade
|
|
53
|
+
"UP",
|
|
54
|
+
# flake8-bugbear
|
|
55
|
+
"B",
|
|
56
|
+
# flake8-simplify
|
|
57
|
+
#"SIM",
|
|
58
|
+
# isort
|
|
59
|
+
"I",
|
|
60
|
+
]
|
|
61
|
+
ignore = []
|
|
62
|
+
|
|
63
|
+
[tool.ruff.lint.extend-per-file-ignores]
|
|
64
|
+
"__init__.py" = ["F401"]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
[tool.mypy]
|
|
68
|
+
strict = false
|
|
69
|
+
exclude = [
|
|
70
|
+
"^venv/.*$",
|
|
71
|
+
"^migrations/.*$",
|
|
72
|
+
"^tests/.*$",
|
|
73
|
+
"^scripts/.*$",
|
|
74
|
+
"^research/.*$",
|
|
75
|
+
]
|
|
76
|
+
plugins = []
|
|
77
|
+
|
|
78
|
+
#[[tool.mypy.overrides]]
|
|
79
|
+
# module = "aiogram.*,apscheduler.*,gspread.*,oauth2client.*"
|
|
80
|
+
# ignore_missing_imports = true
|
|
81
|
+
|
|
82
|
+
[tool.pytest.ini_options]
|
|
83
|
+
testpaths = [
|
|
84
|
+
"tests",
|
|
85
|
+
]
|
|
86
|
+
python_files = "test_*.py"
|
|
87
|
+
pythonpath = ["."]
|
|
88
|
+
log_cli = true
|
|
89
|
+
log_cli_level = "DEBUG"
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
[tool.pylint.messages_control]
|
|
93
|
+
disable = [
|
|
94
|
+
"C0114",
|
|
95
|
+
"C0115",
|
|
96
|
+
"C0116",
|
|
97
|
+
"R0903",
|
|
98
|
+
"R0913",
|
|
99
|
+
"R0914",
|
|
100
|
+
"R0917",
|
|
101
|
+
]
|
|
102
|
+
[tool.pylint.master]
|
|
103
|
+
ignore = [
|
|
104
|
+
".venv",
|
|
105
|
+
"migrations",
|
|
106
|
+
"tests",
|
|
107
|
+
"scripts",
|
|
108
|
+
]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from ._ld_json import find_ld_json, ld_json_iter
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from collections.abc import Iterator
|
|
3
|
+
|
|
4
|
+
from selectolax.lexbor import LexborHTMLParser
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def _fix_unescaped_newlines(content: str) -> str:
|
|
8
|
+
result: list[str] = []
|
|
9
|
+
in_string = False
|
|
10
|
+
escape = False
|
|
11
|
+
|
|
12
|
+
for ch in content:
|
|
13
|
+
if ch == '"' and not escape:
|
|
14
|
+
in_string = not in_string
|
|
15
|
+
if ch == "\n" and in_string:
|
|
16
|
+
result.append("\\n")
|
|
17
|
+
continue
|
|
18
|
+
result.append(ch)
|
|
19
|
+
escape = ch == "\\" and not escape
|
|
20
|
+
|
|
21
|
+
return "".join(result)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def ld_json_iter(parser: LexborHTMLParser) -> Iterator[dict | list]:
|
|
25
|
+
for script_el in parser.css("script[type='application/ld+json']"):
|
|
26
|
+
text = script_el.text()
|
|
27
|
+
try:
|
|
28
|
+
yield json.loads(text)
|
|
29
|
+
except json.JSONDecodeError:
|
|
30
|
+
try:
|
|
31
|
+
yield json.loads(_fix_unescaped_newlines(text))
|
|
32
|
+
except json.JSONDecodeError:
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def find_ld_json(type_: str, parser: LexborHTMLParser) -> dict | None:
|
|
37
|
+
for data in ld_json_iter(parser):
|
|
38
|
+
if isinstance(data, list):
|
|
39
|
+
for item in data:
|
|
40
|
+
if isinstance(item, dict) and item.get("@type") == type_:
|
|
41
|
+
return item
|
|
42
|
+
elif isinstance(data, dict) and data.get("@type") == type_:
|
|
43
|
+
return data
|
|
44
|
+
return None
|
|
File without changes
|