rusticai-playwright 0.0.1__py3-none-any.whl
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.
|
File without changes
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import hashlib
|
|
2
|
+
import logging
|
|
3
|
+
import mimetypes
|
|
4
|
+
import os
|
|
5
|
+
from enum import StrEnum
|
|
6
|
+
from typing import List
|
|
7
|
+
from urllib.parse import urlsplit
|
|
8
|
+
|
|
9
|
+
import shortuuid
|
|
10
|
+
from install_playwright import install
|
|
11
|
+
from markdownify import markdownify as md
|
|
12
|
+
from pydantic import BaseModel, Field
|
|
13
|
+
|
|
14
|
+
from playwright.async_api import async_playwright
|
|
15
|
+
from rustic_ai.core.agents.commons.media import MediaLink
|
|
16
|
+
from rustic_ai.core.agents.commons.message_formats import ErrorMessage
|
|
17
|
+
from rustic_ai.core.guild import Agent, AgentSpec, agent
|
|
18
|
+
from rustic_ai.core.guild.agent_ext.depends.filesystem.filesystem import FileSystem
|
|
19
|
+
from rustic_ai.core.utils.json_utils import JsonDict
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class ScrapingOutputFormat(StrEnum):
|
|
23
|
+
TEXT_HTML = "text/html"
|
|
24
|
+
MARKDOWN = "text/markdown"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class WebScrapingRequest(BaseModel):
|
|
28
|
+
id: str = Field(default_factory=shortuuid.uuid, title="ID of the request")
|
|
29
|
+
links: List[MediaLink] = Field(..., title="URL to scrape")
|
|
30
|
+
output_format: ScrapingOutputFormat = Field(
|
|
31
|
+
default=ScrapingOutputFormat.TEXT_HTML,
|
|
32
|
+
title="Output format of the scraped content",
|
|
33
|
+
description="The format in which the scraped content will be saved. Default is text/html.",
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
transformer_options: JsonDict = Field(
|
|
37
|
+
default={},
|
|
38
|
+
title="Options for the transformer",
|
|
39
|
+
description="Options for the transformer to be applied to the scraped content. Default is an empty dictionary.",
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
class WebScrapingCompleted(BaseModel):
|
|
44
|
+
id: str = Field(..., title="ID of the request")
|
|
45
|
+
links: List[MediaLink] = Field(..., title="URL to scrape")
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class PlaywrightScraperAgent(Agent):
|
|
49
|
+
def __init__(self, agent_spec: AgentSpec):
|
|
50
|
+
super().__init__(agent_spec=agent_spec)
|
|
51
|
+
|
|
52
|
+
@agent.processor(
|
|
53
|
+
WebScrapingRequest, depends_on=[agent.AgentDependency(dependency_key="filesystem", guild_level=True)]
|
|
54
|
+
)
|
|
55
|
+
async def scrape(self, ctx: agent.ProcessContext[WebScrapingRequest], filesystem: FileSystem) -> None:
|
|
56
|
+
async with async_playwright() as p:
|
|
57
|
+
install(p.chromium)
|
|
58
|
+
browser = await p.chromium.launch()
|
|
59
|
+
page = await browser.new_page()
|
|
60
|
+
|
|
61
|
+
scraping_request = ctx.payload
|
|
62
|
+
|
|
63
|
+
scraped_docs: List[MediaLink] = []
|
|
64
|
+
|
|
65
|
+
for link in scraping_request.links:
|
|
66
|
+
url = link.url
|
|
67
|
+
|
|
68
|
+
response = await page.goto(url)
|
|
69
|
+
|
|
70
|
+
if response and response.status != 200:
|
|
71
|
+
ctx.send_error(
|
|
72
|
+
ErrorMessage(
|
|
73
|
+
agent_type=self.get_qualified_class_name(),
|
|
74
|
+
error_type="HTTP_ERROR_{response.status}",
|
|
75
|
+
error_message=f"HTTP error: {response.status} for URL: {url}. Error message: {response.status_text}",
|
|
76
|
+
)
|
|
77
|
+
)
|
|
78
|
+
continue
|
|
79
|
+
|
|
80
|
+
title = await page.title()
|
|
81
|
+
content = await page.content()
|
|
82
|
+
urls = urlsplit(url)
|
|
83
|
+
upath = urls.path
|
|
84
|
+
basename = os.path.basename(upath)
|
|
85
|
+
_, filetype = os.path.splitext(basename)
|
|
86
|
+
|
|
87
|
+
if not filetype and scraping_request.output_format == ScrapingOutputFormat.TEXT_HTML:
|
|
88
|
+
filetype = ".html"
|
|
89
|
+
elif scraping_request.output_format == ScrapingOutputFormat.MARKDOWN:
|
|
90
|
+
filetype = ".md"
|
|
91
|
+
elif not filetype:
|
|
92
|
+
filetype = ".txt"
|
|
93
|
+
|
|
94
|
+
filename = hashlib.md5(content.encode("utf-8")).hexdigest() + filetype
|
|
95
|
+
|
|
96
|
+
mimetype = mimetypes.guess_type(filename)[0]
|
|
97
|
+
|
|
98
|
+
try:
|
|
99
|
+
if scraping_request.output_format == ScrapingOutputFormat.MARKDOWN:
|
|
100
|
+
content = md(
|
|
101
|
+
content,
|
|
102
|
+
**scraping_request.transformer_options,
|
|
103
|
+
)
|
|
104
|
+
with filesystem.open(f"scraped_data/{filename}", "w") as f:
|
|
105
|
+
f.write(content)
|
|
106
|
+
except Exception as e:
|
|
107
|
+
logging.error(f"Error writing file: {e}")
|
|
108
|
+
ctx.send_error(
|
|
109
|
+
ErrorMessage(
|
|
110
|
+
agent_type=self.get_qualified_class_name(),
|
|
111
|
+
error_type="FileWriteError",
|
|
112
|
+
error_message=str(e),
|
|
113
|
+
)
|
|
114
|
+
)
|
|
115
|
+
|
|
116
|
+
meta = link.metadata or {}
|
|
117
|
+
metadata = meta | {"scraped_url": url, "title": title, "request_id": scraping_request.id}
|
|
118
|
+
|
|
119
|
+
output = MediaLink(
|
|
120
|
+
url=f"scraped_data/{filename}",
|
|
121
|
+
name=filename,
|
|
122
|
+
metadata=metadata,
|
|
123
|
+
on_filesystem=True,
|
|
124
|
+
mimetype=mimetype,
|
|
125
|
+
encoding="utf-8",
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
scraped_docs.append(output)
|
|
129
|
+
ctx.send(output)
|
|
130
|
+
|
|
131
|
+
await browser.close()
|
|
132
|
+
|
|
133
|
+
unique_scraped_docs = list({doc.name: doc for doc in scraped_docs}.values())
|
|
134
|
+
|
|
135
|
+
ctx.send(WebScrapingCompleted(id=scraping_request.id, links=unique_scraped_docs))
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: rusticai-playwright
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Rustic AI module for scraping using playwright
|
|
5
|
+
Home-page: https://www.rustic.ai/
|
|
6
|
+
License: Apache-2.0
|
|
7
|
+
Author: Dragonscale Industries Inc.
|
|
8
|
+
Author-email: dev@dragonscale.ai
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
|
+
Provides-Extra: test
|
|
15
|
+
Requires-Dist: install-playwright (>=0.1.1,<0.2.0)
|
|
16
|
+
Requires-Dist: markdownify (>=1.1.0,<2.0.0)
|
|
17
|
+
Requires-Dist: playwright (>=1.51.0,<2.0.0)
|
|
18
|
+
Requires-Dist: rusticai-core (>=0.0.5,<0.1.0)
|
|
19
|
+
Project-URL: Repository, https://github.com/rustic-ai/python-framework
|
|
20
|
+
Project-URL: Rustic AI Core, https://pypi.org/project/rusticai-core/
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Rustic AI Playwright
|
|
24
|
+
|
|
25
|
+
This module employs [Playwright](https://playwright.dev/python/docs/intro) to facilitate web scraping tasks.
|
|
26
|
+
|
|
27
|
+
## Installing
|
|
28
|
+
|
|
29
|
+
```shell
|
|
30
|
+
pip install rusticai-playwright
|
|
31
|
+
```
|
|
32
|
+
**Note:** It depends on [rusticai-core](https://pypi.org/project/rusticai-core/)
|
|
33
|
+
|
|
34
|
+
## Building from Source
|
|
35
|
+
|
|
36
|
+
```shell
|
|
37
|
+
poetry install --with dev
|
|
38
|
+
poetry build
|
|
39
|
+
```
|
|
40
|
+
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
rustic_ai/playwright/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
2
|
+
rustic_ai/playwright/agent.py,sha256=RG-L49eCAN0bix7wHCrrfpBeMctRlP7-11EcTuKSbHM,5135
|
|
3
|
+
rusticai_playwright-0.0.1.dist-info/METADATA,sha256=BZ1jj4cIgmKIhUF3lRDOq0b0CAsbXHFKiKsfCWyUe8A,1225
|
|
4
|
+
rusticai_playwright-0.0.1.dist-info/WHEEL,sha256=Nq82e9rUAnEjt98J6MlVmMCZb-t9cYE2Ir1kpBmnWfs,88
|
|
5
|
+
rusticai_playwright-0.0.1.dist-info/RECORD,,
|