oxylabs-mcp 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.
@@ -0,0 +1,11 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+ .envrc
@@ -0,0 +1,24 @@
1
+ FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim
2
+
3
+ ENV UV_COMPILE_BYTECODE=1
4
+ ENV UV_LINK_MODE=copy
5
+ ENV UV_CACHE_DIR=/opt/uv-cache/
6
+
7
+ RUN apt-get update && apt-get install -y --no-install-recommends git
8
+
9
+ WORKDIR /app
10
+
11
+ RUN --mount=type=cache,target=UV_CACHE_DIR \
12
+ --mount=type=bind,source=uv.lock,target=uv.lock \
13
+ --mount=type=bind,source=pyproject.toml,target=pyproject.toml \
14
+ uv sync --frozen --no-install-project --no-dev --no-editable
15
+
16
+ ADD . /app
17
+
18
+ RUN --mount=type=cache,target=UV_CACHE_DIR \
19
+ uv sync --frozen --no-dev --no-editable
20
+
21
+ # Add virtual environment to PATH
22
+ ENV PATH="/app/.venv/bin:$PATH"
23
+
24
+ ENTRYPOINT ["oxylabs-mcp"]
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Oxylabs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,34 @@
1
+ src_dir := src/
2
+ test_dir := tests/
3
+ current_dir = $(shell pwd)
4
+
5
+ python ?= python3
6
+ virtualenv_dir ?= .venv
7
+
8
+ .PHONY: install_deps
9
+ install_deps: $(virtualenv_dir)
10
+ uv sync
11
+
12
+ .PHONY: lint
13
+ lint: install_deps
14
+ uv run mypy $(src_dir)
15
+ uv run ruff check
16
+
17
+ .PHONY: format
18
+ format: $(virtualenv_dir)
19
+ uv run ruff check --fix .
20
+
21
+ .PHONY: test
22
+ test: install_deps
23
+ uv run pytest ./tests
24
+
25
+ .PHONY: run
26
+ run: install_deps
27
+ npx @modelcontextprotocol/inspector \
28
+ uv \
29
+ --directory $(current_dir) \
30
+ run \
31
+ oxylabs-mcp
32
+
33
+ $(virtualenv_dir):
34
+ $(python) -m venv $@ --symlinks
@@ -0,0 +1,148 @@
1
+ Metadata-Version: 2.4
2
+ Name: oxylabs-mcp
3
+ Version: 0.1.0
4
+ Summary: Oxylabs MCP server
5
+ Project-URL: Homepage, https://github.com/oxylabs/oxylabs-mcp
6
+ Project-URL: Repository, https://github.com/oxylabs/oxylabs-mcp
7
+ Author-email: Augis Braziunas <augis.braziunas@oxylabs.io>
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Classifier: Development Status :: 4 - Beta
11
+ Classifier: Operating System :: OS Independent
12
+ Classifier: Programming Language :: Python :: 3
13
+ Classifier: Programming Language :: Python :: 3.12
14
+ Classifier: Programming Language :: Python :: 3.13
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: httpx>=0.28.1
17
+ Requires-Dist: lxml-html-clean>=0.4.1
18
+ Requires-Dist: lxml>=5.3.0
19
+ Requires-Dist: markdownify>=0.14.1
20
+ Requires-Dist: mcp[cli]>=1.2.0
21
+ Requires-Dist: pydantic>=2.10.5
22
+ Description-Content-Type: text/markdown
23
+
24
+ # MCP Server for Oxylabs Scraper
25
+ [![smithery badge](https://smithery.ai/badge/@oxylabs/oxylabs-mcp)](https://smithery.ai/server/@oxylabs/oxylabs-mcp)
26
+
27
+ The scraper tool is an asynchronous utility that leverages the Oxylabs Web Scraper API to fetch and process content from a given URL. This tool is designed to handle various scraping scenarios efficiently, with flexible options for parsing and rendering web pages.
28
+
29
+ ## Key Features
30
+
31
+ 1. **URL Scraping**
32
+ - Fetches the content of a specified URL using Oxylabs' API.
33
+
34
+ 2. **HTML Parsing**
35
+ - If parsing is enabled, the content is returned in a processed format, suitable for handling by downstream systems like language models (LLMs).
36
+ - If parsing is disabled, the tool strips unnecessary HTML tags and converts the cleaned content into a Markdown file for easier readability and compatibility.
37
+
38
+ 3. **JavaScript Rendering**
39
+ - Offers an option to render pages using a headless browser, which ensures dynamic content (e.g., JavaScript-rendered pages) is included in the output.
40
+ - Supports the html rendering option or skipping rendering entirely.
41
+
42
+ ## Examples on how to query Claude or other LLM
43
+ - Could you scrape https://oxylabs.io page?
44
+ - Scrape https://www.amazon.de/-/en/Smartphone-Contract-Function-Manufacturer-Exclusive/dp/B0CNKD651V with parse enabled.
45
+ - Scrape https://www.amazon.de/-/en/gp/bestsellers/beauty/ref=zg_bs_nav_beauty_0 with parse and render enabled.
46
+ - Use web unblocker with render to scrape https://oxylabs.io/
47
+
48
+ ## Prerequisites
49
+ ### Install uv first.
50
+ https://docs.astral.sh/uv/getting-started/installation/#installation-methods
51
+
52
+ ## Setup Intructions
53
+
54
+ ### Installing via Smithery
55
+
56
+ To install Oxylabs MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@oxylabs/oxylabs-mcp):
57
+
58
+ ```bash
59
+ npx -y @smithery/cli install @oxylabs/oxylabs-mcp --client claude
60
+ ```
61
+
62
+
63
+ ### Setup with Claude Desktop
64
+ ```json
65
+ # claude_desktop_config.json
66
+ # Can find location through:
67
+ # Claude -> Settings -> Developer -> Edit Config
68
+ {
69
+ "mcpServers": {
70
+ "oxylabs_scraper": {
71
+ "command": "uvx",
72
+ "args": ["oxylabs-mcp"],
73
+ "env": {
74
+ "OXYLABS_USERNAME": "YOUR_USERNAME_HERE",
75
+ "OXYLABS_PASSWORD": "YOUR_PASSWORD_HERE",
76
+ }
77
+ }
78
+ }
79
+ }
80
+ ```
81
+
82
+ ## Local/Dev Setup Instructions
83
+ ### Clone repo
84
+ `git clone <git:url>`
85
+ ### Install dependencies
86
+ Install MCP server dependencies:
87
+ ```bash
88
+ cd mcp-server-oxylabs
89
+
90
+ # Create virtual environment and activate it
91
+ uv venv
92
+
93
+ source .venv/bin/activate # MacOS/Linux
94
+ # OR
95
+ .venv/Scripts/activate # Windows
96
+
97
+ # Install dependencies
98
+ uv sync
99
+ ```
100
+ ### Setup with Claude Desktop
101
+ ```json
102
+ # claude_desktop_config.json
103
+ # Can find location through:
104
+ # Claude -> Settings -> Developer -> Edit Config
105
+ {
106
+ "mcpServers": {
107
+ "oxylabs_scraper": {
108
+ "command": "uv",
109
+ "args": [
110
+ "--directory",
111
+ "/<Absolute-path-to-folder>/oxylabs-mcp",
112
+ "run",
113
+ "oxylabs-mcp"
114
+ ],
115
+ "env": {
116
+ "OXYLABS_USERNAME": "YOUR_USERNAME_HERE",
117
+ "OXYLABS_PASSWORD": "YOUR_PASSWORD_HERE"
118
+ }
119
+ }
120
+ }
121
+ }
122
+ ```
123
+
124
+ ### Debugging
125
+ Run:
126
+ ```bash
127
+ make run
128
+ ```
129
+ Then access MCP Inspector at `http://localhost:5173`. You may need to add your username and password in the environment variables in the inspector under `OXYLABS_USERNAME` and `OXYLABS_PASSWORD`.
130
+
131
+ ---
132
+ **NOTE**
133
+
134
+ Usage with the MCP inspector is affected by an ongoing issue with the Python SDK for MCP, see: https://github.com/modelcontextprotocol/python-sdk/pull/85. For Claude, a forked version of the SDK is used as a temporary fix.
135
+
136
+ ---
137
+
138
+ ## License
139
+
140
+ This project is licensed under the [MIT License](LICENSE).
141
+
142
+ ## About Oxylabs
143
+
144
+ Established in 2015, Oxylabs are a market-leading web intelligence collection
145
+ platform, driven by the highest business, ethics, and compliance standards,
146
+ enabling companies worldwide to unlock data-driven insights.
147
+
148
+ [![image](https://oxylabs.io/images/og-image.png)](https://oxylabs.io/)
@@ -0,0 +1,125 @@
1
+ # MCP Server for Oxylabs Scraper
2
+ [![smithery badge](https://smithery.ai/badge/@oxylabs/oxylabs-mcp)](https://smithery.ai/server/@oxylabs/oxylabs-mcp)
3
+
4
+ The scraper tool is an asynchronous utility that leverages the Oxylabs Web Scraper API to fetch and process content from a given URL. This tool is designed to handle various scraping scenarios efficiently, with flexible options for parsing and rendering web pages.
5
+
6
+ ## Key Features
7
+
8
+ 1. **URL Scraping**
9
+ - Fetches the content of a specified URL using Oxylabs' API.
10
+
11
+ 2. **HTML Parsing**
12
+ - If parsing is enabled, the content is returned in a processed format, suitable for handling by downstream systems like language models (LLMs).
13
+ - If parsing is disabled, the tool strips unnecessary HTML tags and converts the cleaned content into a Markdown file for easier readability and compatibility.
14
+
15
+ 3. **JavaScript Rendering**
16
+ - Offers an option to render pages using a headless browser, which ensures dynamic content (e.g., JavaScript-rendered pages) is included in the output.
17
+ - Supports the html rendering option or skipping rendering entirely.
18
+
19
+ ## Examples on how to query Claude or other LLM
20
+ - Could you scrape https://oxylabs.io page?
21
+ - Scrape https://www.amazon.de/-/en/Smartphone-Contract-Function-Manufacturer-Exclusive/dp/B0CNKD651V with parse enabled.
22
+ - Scrape https://www.amazon.de/-/en/gp/bestsellers/beauty/ref=zg_bs_nav_beauty_0 with parse and render enabled.
23
+ - Use web unblocker with render to scrape https://oxylabs.io/
24
+
25
+ ## Prerequisites
26
+ ### Install uv first.
27
+ https://docs.astral.sh/uv/getting-started/installation/#installation-methods
28
+
29
+ ## Setup Intructions
30
+
31
+ ### Installing via Smithery
32
+
33
+ To install Oxylabs MCP server for Claude Desktop automatically via [Smithery](https://smithery.ai/server/@oxylabs/oxylabs-mcp):
34
+
35
+ ```bash
36
+ npx -y @smithery/cli install @oxylabs/oxylabs-mcp --client claude
37
+ ```
38
+
39
+
40
+ ### Setup with Claude Desktop
41
+ ```json
42
+ # claude_desktop_config.json
43
+ # Can find location through:
44
+ # Claude -> Settings -> Developer -> Edit Config
45
+ {
46
+ "mcpServers": {
47
+ "oxylabs_scraper": {
48
+ "command": "uvx",
49
+ "args": ["oxylabs-mcp"],
50
+ "env": {
51
+ "OXYLABS_USERNAME": "YOUR_USERNAME_HERE",
52
+ "OXYLABS_PASSWORD": "YOUR_PASSWORD_HERE",
53
+ }
54
+ }
55
+ }
56
+ }
57
+ ```
58
+
59
+ ## Local/Dev Setup Instructions
60
+ ### Clone repo
61
+ `git clone <git:url>`
62
+ ### Install dependencies
63
+ Install MCP server dependencies:
64
+ ```bash
65
+ cd mcp-server-oxylabs
66
+
67
+ # Create virtual environment and activate it
68
+ uv venv
69
+
70
+ source .venv/bin/activate # MacOS/Linux
71
+ # OR
72
+ .venv/Scripts/activate # Windows
73
+
74
+ # Install dependencies
75
+ uv sync
76
+ ```
77
+ ### Setup with Claude Desktop
78
+ ```json
79
+ # claude_desktop_config.json
80
+ # Can find location through:
81
+ # Claude -> Settings -> Developer -> Edit Config
82
+ {
83
+ "mcpServers": {
84
+ "oxylabs_scraper": {
85
+ "command": "uv",
86
+ "args": [
87
+ "--directory",
88
+ "/<Absolute-path-to-folder>/oxylabs-mcp",
89
+ "run",
90
+ "oxylabs-mcp"
91
+ ],
92
+ "env": {
93
+ "OXYLABS_USERNAME": "YOUR_USERNAME_HERE",
94
+ "OXYLABS_PASSWORD": "YOUR_PASSWORD_HERE"
95
+ }
96
+ }
97
+ }
98
+ }
99
+ ```
100
+
101
+ ### Debugging
102
+ Run:
103
+ ```bash
104
+ make run
105
+ ```
106
+ Then access MCP Inspector at `http://localhost:5173`. You may need to add your username and password in the environment variables in the inspector under `OXYLABS_USERNAME` and `OXYLABS_PASSWORD`.
107
+
108
+ ---
109
+ **NOTE**
110
+
111
+ Usage with the MCP inspector is affected by an ongoing issue with the Python SDK for MCP, see: https://github.com/modelcontextprotocol/python-sdk/pull/85. For Claude, a forked version of the SDK is used as a temporary fix.
112
+
113
+ ---
114
+
115
+ ## License
116
+
117
+ This project is licensed under the [MIT License](LICENSE).
118
+
119
+ ## About Oxylabs
120
+
121
+ Established in 2015, Oxylabs are a market-leading web intelligence collection
122
+ platform, driven by the highest business, ethics, and compliance standards,
123
+ enabling companies worldwide to unlock data-driven insights.
124
+
125
+ [![image](https://oxylabs.io/images/og-image.png)](https://oxylabs.io/)
@@ -0,0 +1,53 @@
1
+ [project]
2
+ name = "oxylabs-mcp"
3
+ version = "0.1.0"
4
+ description = "Oxylabs MCP server"
5
+ authors = [
6
+ {name="Augis Braziunas", email="augis.braziunas@oxylabs.io"},
7
+ ]
8
+ readme = "README.md"
9
+ requires-python = ">=3.12"
10
+ classifiers = [
11
+ "Programming Language :: Python :: 3",
12
+ "Programming Language :: Python :: 3.12",
13
+ "Programming Language :: Python :: 3.13",
14
+ "Development Status :: 4 - Beta",
15
+ "Operating System :: OS Independent",
16
+ ]
17
+
18
+ license = "MIT"
19
+ license-files = ["LICEN[CS]E*"]
20
+
21
+ dependencies = [
22
+ "httpx>=0.28.1",
23
+ "lxml>=5.3.0",
24
+ "lxml-html-clean>=0.4.1",
25
+ "markdownify>=0.14.1",
26
+ "mcp[cli]>=1.2.0",
27
+ "pydantic>=2.10.5",
28
+ ]
29
+
30
+ [dependency-groups]
31
+ dev = [
32
+ "mypy>=1.14.1",
33
+ "pytest>=8.3.4",
34
+ "pytest-asyncio>=0.25.2",
35
+ "pytest-mock>=3.14.0",
36
+ "ruff>=0.9.1",
37
+ ]
38
+
39
+ [build-system]
40
+ requires = ["hatchling"]
41
+ build-backend = "hatchling.build"
42
+
43
+ # Fork with minimal fix for Claude disconnection errors
44
+ # When https://github.com/modelcontextprotocol/python-sdk/pull/85 merged, change back to official lib.
45
+ [tool.uv.sources]
46
+ mcp = { git = "https://github.com/txbm/python-sdk", rev = "19185626d57fdd670219784d10859c7d0a302c2e" }
47
+
48
+ [project.scripts]
49
+ oxylabs-mcp = "oxylabs_mcp:main"
50
+
51
+ [project.urls]
52
+ Homepage = "https://github.com/oxylabs/oxylabs-mcp"
53
+ Repository = "https://github.com/oxylabs/oxylabs-mcp"
@@ -0,0 +1,21 @@
1
+ # Smithery configuration file: https://smithery.ai/docs/deployments
2
+
3
+ startCommand:
4
+ type: stdio
5
+ configSchema:
6
+ # JSON Schema defining the configuration options for the MCP.
7
+ type: object
8
+ required:
9
+ - oxylabsUsername
10
+ - oxylabsPassword
11
+ properties:
12
+ oxylabsUsername:
13
+ type: string
14
+ description: The username for Oxylabs API authentication.
15
+ oxylabsPassword:
16
+ type: string
17
+ description: The password for Oxylabs API authentication.
18
+ commandFunction:
19
+ # A function that produces the CLI command to start the MCP on stdio.
20
+ |-
21
+ config => ({ command: 'oxylabs-mcp', env: { OXYLABS_USERNAME: config.oxylabsUsername, OXYLABS_PASSWORD: config.oxylabsPassword } })
@@ -0,0 +1,10 @@
1
+ from . import server
2
+
3
+
4
+ def main():
5
+ """Main entry point for the package."""
6
+ server.main()
7
+
8
+
9
+ # Optionally expose other important items at package level
10
+ __all__ = ["main", "server"]
@@ -0,0 +1,132 @@
1
+ from typing import Annotated, Any, Literal
2
+ from httpx import AsyncClient, BasicAuth, Timeout, HTTPStatusError, RequestError
3
+ from mcp.server.fastmcp import FastMCP
4
+ from dotenv import load_dotenv
5
+ from pydantic import Field
6
+
7
+ from oxylabs_mcp.utils import convert_html_to_md, get_auth_from_env, strip_html
8
+
9
+ OXYLABS_SCRAPER_URL = "https://realtime.oxylabs.io/v1/queries"
10
+ REQUEST_TIMEOUT = 100
11
+
12
+ mcp = FastMCP("oxylabs_mcp", dependencies=["mcp", "httpx"])
13
+ load_dotenv()
14
+
15
+
16
+ @mcp.tool(name="oxylabs_scraper", description="Scrape url using Oxylabs Web Api")
17
+ async def scrape_url(
18
+ url: Annotated[str, Field(description="Url to scrape")],
19
+ parse: Annotated[
20
+ bool | None,
21
+ Field(
22
+ description="Should result be parsed. "
23
+ "If result should not be parsed then html "
24
+ "will be stripped and converted to markdown file"
25
+ )
26
+ ] = None,
27
+ render: Annotated[
28
+ Literal["html", "None"] | None,
29
+ Field(
30
+ description="Whether a headless browser should be used "
31
+ "to render the page. See: "
32
+ "https://developers.oxylabs.io/scraper-apis/web-scraper-api/features/javascript-rendering "
33
+ "`html` will return rendered html page "
34
+ "`None` will not use render for scraping."
35
+ )
36
+ ] = None
37
+ ) -> str:
38
+ """Scrape Url using Oxylabs scraper api"""
39
+ username, password = get_auth_from_env()
40
+
41
+ async with AsyncClient(
42
+ auth=BasicAuth(username=username, password=password),
43
+ timeout=Timeout(REQUEST_TIMEOUT)
44
+ ) as client:
45
+ try:
46
+ json: dict[str, Any] = {"url": url}
47
+ if parse:
48
+ json["parse"] = bool(parse)
49
+ if render and render != "None":
50
+ json["render"] = render
51
+
52
+ response = await client.post(
53
+ OXYLABS_SCRAPER_URL,
54
+ json=json,
55
+ )
56
+ response.raise_for_status()
57
+
58
+ if not bool(parse):
59
+ striped_html = strip_html(
60
+ str(response.json()["results"][0]["content"])
61
+ )
62
+ return convert_html_to_md(striped_html)
63
+ return str(response.json()["results"][0]["content"])
64
+ except HTTPStatusError as e:
65
+ return (
66
+ "HTTP error during POST request: "
67
+ f"{e.response.status_code} - {e.response.text}"
68
+ )
69
+ except RequestError as e:
70
+ return f"Request error during POST request: {e}"
71
+ except Exception as e:
72
+ return f"Error: {str(e) or repr(e)}"
73
+
74
+
75
+ @mcp.tool(
76
+ name="oxylabs_web_unblocker",
77
+ description="Scrape url using Oxylabs Web Unblocker",
78
+ )
79
+ async def scrape_with_web_unblocker(
80
+ url: Annotated[str, Field(description="Url to scrape with web unblocker")],
81
+ render: Annotated[
82
+ Literal["html", "None"] | None,
83
+ Field(
84
+ description="Whether a headless browser should be used "
85
+ "to render the page. See: "
86
+ "https://developers.oxylabs.io/advanced-proxy-solutions/web-unblocker/headless-browser/javascript-rendering "
87
+ "`html` will return rendered html page "
88
+ "`None` will not use render for scraping."
89
+ )
90
+ ] = None
91
+ ) -> str:
92
+ """Web Unblocker is an AI-powered proxy solution.
93
+
94
+ This tool manages the unblocking process to extract public data
95
+ even from the most difficult websites.
96
+ """
97
+ username, password = get_auth_from_env()
98
+
99
+ proxy = f"http://{username}:{password}@unblock.oxylabs.io:60000"
100
+
101
+ headers: dict[str, Any] = {}
102
+ if render and render != "None":
103
+ headers["X-Oxylabs-Render"] = render
104
+
105
+ async with AsyncClient(
106
+ timeout=Timeout(REQUEST_TIMEOUT),
107
+ verify=False,
108
+ proxy=proxy,
109
+ headers=headers
110
+ ) as client:
111
+ try:
112
+ response = await client.get(url)
113
+ response.raise_for_status()
114
+ striped_html = strip_html(response.text)
115
+ return convert_html_to_md(striped_html)
116
+ except HTTPStatusError as e:
117
+ return (
118
+ "HTTP error during POST request: "
119
+ f"{e.response.status_code} - {e.response.text}"
120
+ )
121
+ except RequestError as e:
122
+ return f"Request error during POST request: {e}"
123
+ except Exception as e:
124
+ return f"Error: {str(e) or repr(e)}"
125
+
126
+
127
+ def main():
128
+ mcp.run()
129
+
130
+
131
+ if __name__ == "__main__":
132
+ main()
@@ -0,0 +1,96 @@
1
+ import os
2
+ from lxml.html.clean import Cleaner
3
+ from lxml.html import defs, fromstring, tostring
4
+ import re
5
+ from markdownify import markdownify as md
6
+
7
+
8
+ def get_auth_from_env() -> tuple[str, str]:
9
+ """Gets Username and password from enviroment variables"""
10
+ USERNAME = os.getenv('OXYLABS_USERNAME')
11
+ PASSWORD = os.getenv('OXYLABS_PASSWORD')
12
+
13
+ if not USERNAME or not PASSWORD:
14
+ raise ValueError(
15
+ "OXYLABS_USERNAME and OXYLABS_PASSWORD "
16
+ "must be set in the environment variables."
17
+ )
18
+ return USERNAME, PASSWORD
19
+
20
+
21
+ def clean_html(html: str):
22
+ cleaner = Cleaner(
23
+ scripts=True,
24
+ javascript=True,
25
+ style=True,
26
+ remove_tags=[],
27
+ kill_tags=["nav", "svg", "footer", "noscript", "script", "form"],
28
+ safe_attrs=list(defs.safe_attrs) + ["idx"],
29
+ comments=True,
30
+ inline_style=True,
31
+ links=True,
32
+ meta=False,
33
+ page_structure=False,
34
+ embedded=True,
35
+ frames=False,
36
+ forms=False,
37
+ annoying_tags=False,
38
+ )
39
+ return cleaner.clean_html(html)
40
+
41
+
42
+ def strip_html(html: str) -> str:
43
+ """
44
+ Cleans and simplifies an HTML string by removing unwanted elements,
45
+ attributes, and redundant content.
46
+
47
+ Args:
48
+ html (str): The input HTML string.
49
+
50
+ Returns:
51
+ str: The cleaned and simplified HTML string.
52
+ """
53
+ cleaned_html = clean_html(html)
54
+ html_tree = fromstring(cleaned_html)
55
+
56
+ for element in html_tree.iter():
57
+ # Remove style attributes.
58
+ if "style" in element.attrib:
59
+ del element.attrib["style"]
60
+
61
+ # Remove elements that have no attributes, no content and no children.
62
+ if (
63
+ (
64
+ not element.attrib
65
+ or (len(element.attrib) == 1 and "idx" in element.attrib)
66
+ )
67
+ and not element.getchildren()
68
+ and (not element.text or not element.text.strip())
69
+ and (not element.tail or not element.tail.strip())
70
+ ):
71
+ parent = element.getparent()
72
+ if parent is not None:
73
+ parent.remove(element)
74
+
75
+ # Remove elements with footer and hidden in class or id
76
+ xpath_query = (
77
+ ".//*[contains(@class, 'footer') or contains(@id, 'footer') or "
78
+ "contains(@class, 'hidden') or contains(@id, 'hidden')]")
79
+ elements_to_remove = html_tree.xpath(xpath_query)
80
+ for element in elements_to_remove:
81
+ parent = element.getparent()
82
+ if parent is not None:
83
+ parent.remove(element)
84
+
85
+ # Serialize the HTML tree back to a string
86
+ stripped_html = tostring(html_tree, encoding="unicode")
87
+ # Previous cleaning produces empty spaces.
88
+ # Replace multiple spaces with an single one
89
+ stripped_html = re.sub(r"\s{2,}", " ", stripped_html)
90
+ # Replace consecutive newlines with an empty string
91
+ stripped_html = re.sub(r"\n{2,}", "", stripped_html)
92
+ return stripped_html
93
+
94
+
95
+ def convert_html_to_md(html: str) -> str:
96
+ return md(html)
File without changes
File without changes
@@ -0,0 +1,2 @@
1
+ <html> <body> <div class="content"> <p>Welcome to my website</p> </div> <div class="other"> <p>Visible content</p> </div> </body>
2
+ </html>