Merge pull request #9 from D4Vinci/dev

v0.2.2
This commit is contained in:
Karim shoair
2024-11-16 22:04:03 +02:00
committed by GitHub
10 changed files with 41 additions and 18 deletions
+12 -3
View File
@@ -6,9 +6,9 @@ Dealing with failing web scrapers due to anti-bot protections or website changes
Scrapling is a high-performance, intelligent web scraping library for Python that automatically adapts to website changes while significantly outperforming popular alternatives. For both beginners and experts, Scrapling provides powerful features while maintaining simplicity. Scrapling is a high-performance, intelligent web scraping library for Python that automatically adapts to website changes while significantly outperforming popular alternatives. For both beginners and experts, Scrapling provides powerful features while maintaining simplicity.
```python ```python
>> from scrapling import Fetcher, StealthyFetcher, PlayWrightFetcher >> from scrapling.default import Fetcher, StealthyFetcher, PlayWrightFetcher
# Fetch websites' source under the radar! # Fetch websites' source under the radar!
>> page = StealthyFetcher().fetch('https://example.com', headless=True, network_idle=True) >> page = StealthyFetcher.fetch('https://example.com', headless=True, network_idle=True)
>> print(page.status) >> print(page.status)
200 200
>> products = page.css('.product', auto_save=True) # Scrape data that survives website design changes! >> products = page.css('.product', auto_save=True) # Scrape data that survives website design changes!
@@ -211,12 +211,21 @@ python -m browserforge update
``` ```
## Fetching Websites Features ## Fetching Websites Features
All fetcher-type classes are imported in the same way You might be a little bit confused by now so let me clear things up. All fetcher-type classes are imported in the same way
```python ```python
from scrapling import Fetcher, StealthyFetcher, PlayWrightFetcher from scrapling import Fetcher, StealthyFetcher, PlayWrightFetcher
``` ```
And all of them can take these initialization arguments: `auto_match`, `huge_tree`, `keep_comments`, `storage`, `storage_args`, and `debug` which are the same ones you give to the `Adaptor` class. And all of them can take these initialization arguments: `auto_match`, `huge_tree`, `keep_comments`, `storage`, `storage_args`, and `debug` which are the same ones you give to the `Adaptor` class.
If you don't want to pass arguments to the generated `Adaptor` object and want to use the default values, you can use this import instead for cleaner code:
```python
from scrapling.default import Fetcher, StealthyFetcher, PlayWrightFetcher
```
then use it right away without initializing like:
```python
page = StealthyFetcher.fetch('https://example.com')
```
Also, the `Response` object returned from all fetchers is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`. All `cookies`, `headers`, and `request_headers` are always of type `dictionary`. Also, the `Response` object returned from all fetchers is the same as `Adaptor` object except it has these added attributes: `status`, `reason`, `cookies`, `headers`, and `request_headers`. All `cookies`, `headers`, and `request_headers` are always of type `dictionary`.
> [!NOTE] > [!NOTE]
> The `auto_match` argument is enabled by default which is the one you should care about the most as you will see later. > The `auto_match` argument is enabled by default which is the one you should care about the most as you will see later.
+1 -1
View File
@@ -4,7 +4,7 @@ from scrapling.parser import Adaptor, Adaptors
from scrapling.core.custom_types import TextHandler, AttributesHandler from scrapling.core.custom_types import TextHandler, AttributesHandler
__author__ = "Karim Shoair (karim.shoair@pm.me)" __author__ = "Karim Shoair (karim.shoair@pm.me)"
__version__ = "0.2.1" __version__ = "0.2.2"
__copyright__ = "Copyright (c) 2024 Karim Shoair" __copyright__ = "Copyright (c) 2024 Karim Shoair"
+6
View File
@@ -0,0 +1,6 @@
from .fetchers import Fetcher, StealthyFetcher, PlayWrightFetcher
# If you are going to use Fetchers with the default settings, import them from this file instead for a cleaner looking code
Fetcher = Fetcher()
StealthyFetcher = StealthyFetcher()
PlayWrightFetcher = PlayWrightFetcher()
+2 -2
View File
@@ -114,14 +114,14 @@ class CamoufoxEngine:
response = Response( response = Response(
url=res.url, url=res.url,
text=page.content(), text=page.content(),
content=res.body(), body=res.body(),
status=res.status, status=res.status,
reason=res.status_text, reason=res.status_text,
encoding=encoding, encoding=encoding,
cookies={cookie['name']: cookie['value'] for cookie in page.context.cookies()}, cookies={cookie['name']: cookie['value'] for cookie in page.context.cookies()},
headers=res.all_headers(), headers=res.all_headers(),
request_headers=res.request.all_headers(), request_headers=res.request.all_headers(),
adaptor_arguments=self.adaptor_arguments **self.adaptor_arguments
) )
page.close() page.close()
+2 -2
View File
@@ -224,14 +224,14 @@ class PlaywrightEngine:
response = Response( response = Response(
url=res.url, url=res.url,
text=page.content(), text=page.content(),
content=res.body(), body=res.body(),
status=res.status, status=res.status,
reason=res.status_text, reason=res.status_text,
encoding=encoding, encoding=encoding,
cookies={cookie['name']: cookie['value'] for cookie in page.context.cookies()}, cookies={cookie['name']: cookie['value'] for cookie in page.context.cookies()},
headers=res.all_headers(), headers=res.all_headers(),
request_headers=res.request.all_headers(), request_headers=res.request.all_headers(),
adaptor_arguments=self.adaptor_arguments **self.adaptor_arguments
) )
page.close() page.close()
return response return response
+2 -2
View File
@@ -53,14 +53,14 @@ class StaticEngine:
return Response( return Response(
url=str(response.url), url=str(response.url),
text=response.text, text=response.text,
content=response.content, body=response.content,
status=response.status_code, status=response.status_code,
reason=response.reason_phrase, reason=response.reason_phrase,
encoding=response.encoding or 'utf-8', encoding=response.encoding or 'utf-8',
cookies=dict(response.cookies), cookies=dict(response.cookies),
headers=dict(response.headers), headers=dict(response.headers),
request_headers=dict(response.request.headers), request_headers=dict(response.request.headers),
adaptor_arguments=self.adaptor_arguments **self.adaptor_arguments
) )
def get(self, url: str, stealthy_headers: Optional[bool] = True, **kwargs: Dict) -> Response: def get(self, url: str, stealthy_headers: Optional[bool] = True, **kwargs: Dict) -> Response:
+3 -4
View File
@@ -12,15 +12,14 @@ from scrapling.core._types import Any, List, Type, Union, Optional, Dict, Callab
class Response(Adaptor): class Response(Adaptor):
"""This class is returned by all engines as a way to unify response type between different libraries.""" """This class is returned by all engines as a way to unify response type between different libraries."""
def __init__(self, url: str, text: str, content: bytes, status: int, reason: str, cookies: Dict, headers: Dict, request_headers: Dict, adaptor_arguments: Dict, encoding: str = 'utf-8'): def __init__(self, url: str, text: str, body: bytes, status: int, reason: str, cookies: Dict, headers: Dict, request_headers: Dict, encoding: str = 'utf-8', **adaptor_arguments: Dict):
automatch_domain = adaptor_arguments.pop('automatch_domain', None) automatch_domain = adaptor_arguments.pop('automatch_domain', None)
super().__init__(text=text, body=content, url=automatch_domain or url, encoding=encoding, **adaptor_arguments)
self.status = status self.status = status
self.reason = reason self.reason = reason
self.cookies = cookies self.cookies = cookies
self.headers = headers self.headers = headers
self.request_headers = request_headers self.request_headers = request_headers
super().__init__(text=text, body=body, url=automatch_domain or url, encoding=encoding, **adaptor_arguments)
# For back-ward compatibility # For back-ward compatibility
self.adaptor = self self.adaptor = self
@@ -31,7 +30,7 @@ class Response(Adaptor):
class BaseFetcher: class BaseFetcher:
def __init__( def __init__(
self, huge_tree: bool = True, keep_comments: Optional[bool] = False, auto_match: Optional[bool] = True, self, huge_tree: bool = True, keep_comments: Optional[bool] = False, auto_match: Optional[bool] = True,
storage: Any = SQLiteStorageSystem, storage_args: Optional[Dict] = None, debug: Optional[bool] = True, storage: Any = SQLiteStorageSystem, storage_args: Optional[Dict] = None, debug: Optional[bool] = False,
automatch_domain: Optional[str] = None, automatch_domain: Optional[str] = None,
): ):
"""Arguments below are the same from the Adaptor class so you can pass them directly, the rest of Adaptor's arguments """Arguments below are the same from the Adaptor class so you can pass them directly, the rest of Adaptor's arguments
+11 -2
View File
@@ -32,6 +32,7 @@ class Adaptor(SelectorsGeneration):
storage: Any = SQLiteStorageSystem, storage: Any = SQLiteStorageSystem,
storage_args: Optional[Dict] = None, storage_args: Optional[Dict] = None,
debug: Optional[bool] = True, debug: Optional[bool] = True,
**kwargs
): ):
"""The main class that works as a wrapper for the HTML input data. Using this class, you can search for elements """The main class that works as a wrapper for the HTML input data. Using this class, you can search for elements
with expressions in CSS, XPath, or with simply text. Check the docs for more info. with expressions in CSS, XPath, or with simply text. Check the docs for more info.
@@ -117,6 +118,10 @@ class Adaptor(SelectorsGeneration):
self.__attributes = None self.__attributes = None
self.__tag = None self.__tag = None
self.__debug = debug self.__debug = debug
# No need to check if all response attributes exist or not because if `status` exist, then the rest exist (Save some CPU cycles for speed)
self.__response_data = {
key: getattr(self, key) for key in ('status', 'reason', 'cookies', 'headers', 'request_headers',)
} if hasattr(self, 'status') else {}
# Node functionalities, I wanted to move to separate Mixin class but it had slight impact on performance # Node functionalities, I wanted to move to separate Mixin class but it had slight impact on performance
@staticmethod @staticmethod
@@ -138,10 +143,14 @@ class Adaptor(SelectorsGeneration):
return TextHandler(str(element)) return TextHandler(str(element))
else: else:
if issubclass(type(element), html.HtmlMixin): if issubclass(type(element), html.HtmlMixin):
return self.__class__( return self.__class__(
root=element, url=self.url, encoding=self.encoding, auto_match=self.__auto_match_enabled, root=element,
text='', body=b'', # Since root argument is provided, both `text` and `body` will be ignored so this is just a filler
url=self.url, encoding=self.encoding, auto_match=self.__auto_match_enabled,
keep_comments=True, # if the comments are already removed in initialization, no need to try to delete them in sub-elements keep_comments=True, # if the comments are already removed in initialization, no need to try to delete them in sub-elements
huge_tree=self.__huge_tree_enabled, debug=self.__debug huge_tree=self.__huge_tree_enabled, debug=self.__debug,
**self.__response_data
) )
return element return element
+1 -1
View File
@@ -1,6 +1,6 @@
[metadata] [metadata]
name = scrapling name = scrapling
version = 0.2.1 version = 0.2.2
author = Karim Shoair author = Karim Shoair
author_email = karim.shoair@pm.me author_email = karim.shoair@pm.me
description = Scrapling is an undetectable, powerful, flexible, adaptive, and high-performance web scraping library for Python. description = Scrapling is an undetectable, powerful, flexible, adaptive, and high-performance web scraping library for Python.
+1 -1
View File
@@ -6,7 +6,7 @@ with open("README.md", "r", encoding="utf-8") as fh:
setup( setup(
name="scrapling", name="scrapling",
version="0.2.1", version="0.2.2",
description="""Scrapling is a powerful, flexible, and high-performance web scraping library for Python. It description="""Scrapling is a powerful, flexible, and high-performance web scraping library for Python. It
simplifies the process of extracting data from websites, even when they undergo structural changes, and offers simplifies the process of extracting data from websites, even when they undergo structural changes, and offers
impressive speed improvements over many popular scraping tools.""", impressive speed improvements over many popular scraping tools.""",