GraphQL API hacking Series for bug hunters 01
Understanding GraphQL and Its Security ImplicationsContinue reading on Medium »
Read more...
Understanding GraphQL and Its Security ImplicationsContinue reading on Medium »
Read more...
Medium
GraphQL API hacking Series for Bug Hunters Part 01
Understanding GraphQL and Its Security Implications
JWT, Meet Me Outside: How I Decoded, Re-Signed, and Owned the App
Hey there!😁Continue reading on InfoSec Write-ups »
Read more...
Hey there!😁Continue reading on InfoSec Write-ups »
Read more...
Medium
🧠 JWT, Meet Me Outside: How I Decoded, Re-Signed, and Owned the App 🔐💥
Hey there!😁
How I Earned $8947 bounty for Remote Code Execution via a Hijacked GitHub Module
Continue reading on Medium »
Read more...
Continue reading on Medium »
Read more...
Medium
How I Earned $8947 💰bounty for Remote Code Execution via a Hijacked GitHub Module
Earlier this year, I discovered a High severity Remote Code Execution (RCE) vulnerability affecting a big company (referred to as Redacted for confidentiality). Through a combination of my…
POC — CVE-2025–29306 FOXCMS /images/index.html Code Execution Vulnerability
OverviewContinue reading on Medium »
Read more...
OverviewContinue reading on Medium »
Read more...
Medium
POC — CVE-2025–29306 FOXCMS /images/index.html Code Execution Vulnerability
Overview
How I Earned $8947 bounty for Remote Code Execution via a Hijacked GitHub Module
https://nvk0x.medium.com/how-i-earned-8947-bounty-for-remote-code-execution-via-a-hijacked-github-module-91c4a4b63255?source=rss------bug_bounty-5
https://nvk0x.medium.com/how-i-earned-8947-bounty-for-remote-code-execution-via-a-hijacked-github-module-91c4a4b63255?source=rss------bug_bounty-5
POC — CVE-2025–29306 FOXCMS /images/index.html Code Execution Vulnerability
https://medium.com/@verylazytech/poc-cve-2025-29306-foxcms-images-index-html-code-execution-vulnerability-0c4db3905fd0?source=rss------bug_bounty-5
https://medium.com/@verylazytech/poc-cve-2025-29306-foxcms-images-index-html-code-execution-vulnerability-0c4db3905fd0?source=rss------bug_bounty-5
OverviewContinue reading on Medium » (https://medium.com/@verylazytech/poc-cve-2025-29306-foxcms-images-index-html-code-execution-vulnerability-0c4db3905fd0?source=rss------bug_bounty-5)
Simple Tips for Bug Bounty Beginners: Content Spoofing via HTML Injection
NOTE: Make sure to test only on sites where it is allowed to test and carefully read and follow the guidelines for testing on the site.Continue reading on Medium »
Read more...
NOTE: Make sure to test only on sites where it is allowed to test and carefully read and follow the guidelines for testing on the site.Continue reading on Medium »
Read more...
Medium
Simple Tips for Bug Bounty Beginners: Content Spoofing via HTML Injection
NOTE: Make sure to test only on sites where it is allowed to test and carefully read and follow the guidelines for testing on the site.
The $2500 bug: Remote Code Execution via Supply Chain Attack
Hey there!😇Continue reading on Medium »
Read more...
Hey there!😇Continue reading on Medium »
Read more...
Medium
💵 The $2500 bug: Remote Code Execution via Supply Chain Attack
Hey there!😇
Exploiting a Referer Header for Open Redirect
Hello Everyone!Continue reading on Medium »
Read more...
Hello Everyone!Continue reading on Medium »
Read more...
Medium
Exploiting a Referer Header for Open Redirect
Hello Everyone!
I Hijacked Accounts in 10 Minutes (IDOR Bug)
How I Found a Critical IDOR ATO Exploit in HackerOne (2025)Continue reading on Medium »
Read more...
How I Found a Critical IDOR ATO Exploit in HackerOne (2025)Continue reading on Medium »
Read more...
Medium
I Hijacked Accounts in 10 Minutes (IDOR Bug)
How I Found a Critical IDOR ATO Exploit in HackerOne (2025)
# Walkthrough: VulnHub Machine — Ted 1 (Full Root Access)
⚠️ Disclaimer: This write-up is created purely for educational purposes. The testing was performed in a controlled lab environment on a…Continue reading on Medium »
Read more...
⚠️ Disclaimer: This write-up is created purely for educational purposes. The testing was performed in a controlled lab environment on a…Continue reading on Medium »
Read more...
Medium
# Walkthrough: VulnHub Machine — Ted (Full Root Access)
⚠️ Disclaimer: This write-up is created purely for educational purposes. The testing was performed in a controlled lab environment on a…
Scrapling - An Undetectable, Powerful, Flexible, High-Performance Python Library That Makes Web Scraping Simple And Easy Again!
http://www.kitploit.com/2025/04/scrapling-undetectable-powerful.html
http://www.kitploit.com/2025/04/scrapling-undetectable-powerful.html
Dealing with failing web scrapers due to anti-bot protections or website changes? Meet Scrapling. Scrapling is a high-performance (https://www.kitploit.com/search/label/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. >> from scrapling.defaults import Fetcher, AsyncFetcher, StealthyFetcher, PlayWrightFetcher
# Fetch websites' source under the radar!
>> page = StealthyFetcher.fetch('https://example.com', headless=True, network_idle=True)
>> print(page.status)
200
>> products = page.css('.product', auto_save=True) # Scrape data that survives website design changes!
>> # Later, if the website structure changes, pass `auto_match=True`
>> products = page.css('.product', auto_match=True) # and Scrapling still finds them!
Key Features Fetch websites as you prefer with async support HTTP Requests: Fast and stealthy HTTP requests with the Fetcher class. Dynamic Loading & Automation: Fetch dynamic websites with the PlayWrightFetcher class through your real browser, Scrapling's stealth mode, Playwright's Chrome browser, or NSTbrowser (https://app.nstbrowser.io/r/1vO5e5)'s browserless! Anti-bot Protections Bypass: Easily bypass protections with StealthyFetcher and PlayWrightFetcher classes. Adaptive Scraping 🔄 Smart Element Tracking: Relocate elements after website changes, using an intelligent similarity system and integrated storage. 🎯 Flexible Selection: CSS selectors, XPath selectors, filters-based search, text search, regex search and more. 🔍 Find Similar Elements: Automatically locate elements similar to the element you found! 🧠 Smart Content Scraping: Extract data from multiple websites without specific selectors using Scrapling powerful features. High Performance 🚀 Lightning Fast: Built from the ground up with performance in mind, outperforming most popular Python scraping libraries. 🔋 Memory Efficient: Optimized data structures for minimal memory footprint. ⚡ Fast JSON serialization: 10x faster than standard library. Developer Friendly 🛠️ Powerful Navigation API: Easy DOM traversal in all directions. 🧬 Rich Text Processing: All strings have built-in regex, cleaning methods, and more. All elements' attributes are optimized dictionaries that takes less memory than standard dictionaries with added methods. 📝 Auto Selectors Generation: Generate robust short and full CSS/XPath selectors for any element. 🔌 Familiar API: Similar to Scrapy/BeautifulSoup and the same pseudo-elements used in Scrapy. 📘 Type hints: Complete type/doc-strings coverage for future-proofing and best autocompletion support. Getting Started from scrapling.fetchers import Fetcher
fetcher = Fetcher(auto_match=False)
# Do http GET request to a web page and create an Adaptor instance
page = fetcher.get('https://quotes.toscrape.com/', stealthy_headers=True)
# Get all text content from all HTML tags in the page except `script` and `style` tags
page.get_all_text(ignore_tags=('script', 'style'))
# Get all quotes elements, any of these methods will return a list of strings directly (TextHandlers)
quotes = page.css('.quote .text::text') # CSS selector
quotes = page.xpath('//span[@class="text"]/text()') # XPath
quotes = page.css('.quote').css('.text::text') # Chained selectors
quotes = [element.text for element in page.css('.quote .text')] # Slower than bulk query above
# Get the first quote element
quote = page.css_first('.quote') # same as page.css('.quote').first or page.css('.quote')[0]
# Tired of selectors? Use find_all/find
# Get all 'div' HTML tags that one of its 'class' values is 'quote'
quotes = page.find_all('div', {'class': 'quote'})
# Same as
quotes = page.find_all('div', class_='quote')
quotes = page.find_all(['div'], class_='quote')
quotes = page.find_all(class_='quote') # and so on...
# Working with elements
# Fetch websites' source under the radar!
>> page = StealthyFetcher.fetch('https://example.com', headless=True, network_idle=True)
>> print(page.status)
200
>> products = page.css('.product', auto_save=True) # Scrape data that survives website design changes!
>> # Later, if the website structure changes, pass `auto_match=True`
>> products = page.css('.product', auto_match=True) # and Scrapling still finds them!
Key Features Fetch websites as you prefer with async support HTTP Requests: Fast and stealthy HTTP requests with the Fetcher class. Dynamic Loading & Automation: Fetch dynamic websites with the PlayWrightFetcher class through your real browser, Scrapling's stealth mode, Playwright's Chrome browser, or NSTbrowser (https://app.nstbrowser.io/r/1vO5e5)'s browserless! Anti-bot Protections Bypass: Easily bypass protections with StealthyFetcher and PlayWrightFetcher classes. Adaptive Scraping 🔄 Smart Element Tracking: Relocate elements after website changes, using an intelligent similarity system and integrated storage. 🎯 Flexible Selection: CSS selectors, XPath selectors, filters-based search, text search, regex search and more. 🔍 Find Similar Elements: Automatically locate elements similar to the element you found! 🧠 Smart Content Scraping: Extract data from multiple websites without specific selectors using Scrapling powerful features. High Performance 🚀 Lightning Fast: Built from the ground up with performance in mind, outperforming most popular Python scraping libraries. 🔋 Memory Efficient: Optimized data structures for minimal memory footprint. ⚡ Fast JSON serialization: 10x faster than standard library. Developer Friendly 🛠️ Powerful Navigation API: Easy DOM traversal in all directions. 🧬 Rich Text Processing: All strings have built-in regex, cleaning methods, and more. All elements' attributes are optimized dictionaries that takes less memory than standard dictionaries with added methods. 📝 Auto Selectors Generation: Generate robust short and full CSS/XPath selectors for any element. 🔌 Familiar API: Similar to Scrapy/BeautifulSoup and the same pseudo-elements used in Scrapy. 📘 Type hints: Complete type/doc-strings coverage for future-proofing and best autocompletion support. Getting Started from scrapling.fetchers import Fetcher
fetcher = Fetcher(auto_match=False)
# Do http GET request to a web page and create an Adaptor instance
page = fetcher.get('https://quotes.toscrape.com/', stealthy_headers=True)
# Get all text content from all HTML tags in the page except `script` and `style` tags
page.get_all_text(ignore_tags=('script', 'style'))
# Get all quotes elements, any of these methods will return a list of strings directly (TextHandlers)
quotes = page.css('.quote .text::text') # CSS selector
quotes = page.xpath('//span[@class="text"]/text()') # XPath
quotes = page.css('.quote').css('.text::text') # Chained selectors
quotes = [element.text for element in page.css('.quote .text')] # Slower than bulk query above
# Get the first quote element
quote = page.css_first('.quote') # same as page.css('.quote').first or page.css('.quote')[0]
# Tired of selectors? Use find_all/find
# Get all 'div' HTML tags that one of its 'class' values is 'quote'
quotes = page.find_all('div', {'class': 'quote'})
# Same as
quotes = page.find_all('div', class_='quote')
quotes = page.find_all(['div'], class_='quote')
quotes = page.find_all(class_='quote') # and so on...
# Working with elements
Then run this command to install browsers' dependencies needed to use Fetcher classes scrapling install
If you have any installation issues, please open an issue. Fetching Websites Fetchers are interfaces built on top of other libraries with added features that do requests or fetch pages for you in a single request fashion and then return an Adaptor object. This feature was introduced because the only option we had before was to fetch the page as you wanted it, then pass it manually to the Adaptor class to create an Adaptor instance and start playing around with the page. Features You might be slightly confused by now so let me clear things up. All fetcher-type classes are imported in the same way from scrapling.fetchers import Fetcher, StealthyFetcher, PlayWrightFetcher
All of them can take these initialization arguments: auto_match, huge_tree, keep_comments, keep_cdata, storage, and storage_args, 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: from scrapling.defaults import Fetcher, AsyncFetcher, StealthyFetcher, PlayWrightFetcher
then use it right away without initializing like: page = StealthyFetcher.fetch('https://example.com')
Also, the Response object returned from all fetchers is the same as the Adaptor object except it has these added attributes: status, reason, cookies, headers, history, and request_headers. All cookies, headers, and request_headers are always of type dictionary (https://www.kitploit.com/search/label/Dictionary). [!NOTE] The auto_match argument is enabled by default which is the one you should care about the most as you will see later. Fetcher This class is built on top of httpx (https://www.python-httpx.org/) with additional configuration (https://www.kitploit.com/search/label/Configuration) options, here you can do GET, POST, PUT, and DELETE requests. For all methods, you have stealthy_headers which makes Fetcher create and use real browser's headers then create a referer header as if this request came from Google's search of this URL's domain. It's enabled by default. You can also set the number of retries with the argument retries for all methods and this will make httpx retry requests if it failed for any reason. The default number of retries for all Fetcher methods is 3. Hence: All headers generated by stealthy_headers argument can be overwritten by you through the headers argument You can route all traffic (HTTP and HTTPS) to a proxy for any of these methods in this format http://username:password@localhost:8030 >> page = Fetcher().get('https://httpbin.org/get', stealthy_headers=True, follow_redirects=True)
>> page = Fetcher().post('https://httpbin.org/post', data={'key': 'value'}, proxy='http://username:password@localhost:8030')
>> page = Fetcher().put('https://httpbin.org/put', data={'key': 'value'})
>> page = Fetcher().delete('https://httpbin.org/delete')
For Async requests, you will just replace the import like below: >> from scrapling.fetchers import AsyncFetcher
>> page = await AsyncFetcher().get('https://httpbin.org/get', stealthy_headers=True, follow_redirects=True)
>> page = await AsyncFetcher().post('https://httpbin.org/post', data={'key': 'value'}, proxy='http://username:password@localhost:8030')
>> page = await AsyncFetcher().put('https://httpbin.org/put', data={'key': 'value'})
>> page = await AsyncFetcher().delete('https://httpbin.org/delete')
StealthyFetcher This class is built on top of Camoufox (https://github.com/daijro/camoufox), bypassing most anti-bot protections by default. Scrapling adds extra layers of flavors and configurations to increase performance and undetectability even further. >> page = StealthyFetcher().fetch('https://www.browserscan.net/bot-detection') # Running headless by default
>> page.status == 200
True
If you have any installation issues, please open an issue. Fetching Websites Fetchers are interfaces built on top of other libraries with added features that do requests or fetch pages for you in a single request fashion and then return an Adaptor object. This feature was introduced because the only option we had before was to fetch the page as you wanted it, then pass it manually to the Adaptor class to create an Adaptor instance and start playing around with the page. Features You might be slightly confused by now so let me clear things up. All fetcher-type classes are imported in the same way from scrapling.fetchers import Fetcher, StealthyFetcher, PlayWrightFetcher
All of them can take these initialization arguments: auto_match, huge_tree, keep_comments, keep_cdata, storage, and storage_args, 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: from scrapling.defaults import Fetcher, AsyncFetcher, StealthyFetcher, PlayWrightFetcher
then use it right away without initializing like: page = StealthyFetcher.fetch('https://example.com')
Also, the Response object returned from all fetchers is the same as the Adaptor object except it has these added attributes: status, reason, cookies, headers, history, and request_headers. All cookies, headers, and request_headers are always of type dictionary (https://www.kitploit.com/search/label/Dictionary). [!NOTE] The auto_match argument is enabled by default which is the one you should care about the most as you will see later. Fetcher This class is built on top of httpx (https://www.python-httpx.org/) with additional configuration (https://www.kitploit.com/search/label/Configuration) options, here you can do GET, POST, PUT, and DELETE requests. For all methods, you have stealthy_headers which makes Fetcher create and use real browser's headers then create a referer header as if this request came from Google's search of this URL's domain. It's enabled by default. You can also set the number of retries with the argument retries for all methods and this will make httpx retry requests if it failed for any reason. The default number of retries for all Fetcher methods is 3. Hence: All headers generated by stealthy_headers argument can be overwritten by you through the headers argument You can route all traffic (HTTP and HTTPS) to a proxy for any of these methods in this format http://username:password@localhost:8030 >> page = Fetcher().get('https://httpbin.org/get', stealthy_headers=True, follow_redirects=True)
>> page = Fetcher().post('https://httpbin.org/post', data={'key': 'value'}, proxy='http://username:password@localhost:8030')
>> page = Fetcher().put('https://httpbin.org/put', data={'key': 'value'})
>> page = Fetcher().delete('https://httpbin.org/delete')
For Async requests, you will just replace the import like below: >> from scrapling.fetchers import AsyncFetcher
>> page = await AsyncFetcher().get('https://httpbin.org/get', stealthy_headers=True, follow_redirects=True)
>> page = await AsyncFetcher().post('https://httpbin.org/post', data={'key': 'value'}, proxy='http://username:password@localhost:8030')
>> page = await AsyncFetcher().put('https://httpbin.org/put', data={'key': 'value'})
>> page = await AsyncFetcher().delete('https://httpbin.org/delete')
StealthyFetcher This class is built on top of Camoufox (https://github.com/daijro/camoufox), bypassing most anti-bot protections by default. Scrapling adds extra layers of flavors and configurations to increase performance and undetectability even further. >> page = StealthyFetcher().fetch('https://www.browserscan.net/bot-detection') # Running headless by default
>> page.status == 200
True
'div'
>>> quote.parent
...'>
>>> quote.parent.tag
'div'
>>> quote.children
["The...' parent=',
by ,
Tags: ]
>>> quote.siblings
[ ,
,
...]
>>> quote.next # gets the next element, the same logic applies to `quote.previous`
>>> quote.children.css_first(".author::text")
'Albert Einstein'
>>> quote.has_class('quote')
True
# Generate new selectors for any element
>>> quote.generate_css_selector
'body > div > div:nth-of-type(2) > div > div'
# Test these selectors on your favorite browser or reuse them again in the library's methods!
>>> quote.generate_xpath_selector
'//body/div/div[2]/div/div'
If your case needs more than the element's parent, you can iterate over the whole ancestors' tree of any element like below for ancestor in quote.iterancestors():
# do something with it...
You can search for a specific ancestor of an element that satisfies a function, all you need to do is to pass a function that takes an Adaptor object as an argument and return True if the condition satisfies or False otherwise like below: >>> quote.find_ancestor(lambda ancestor: ancestor.has_class('row'))
...' parent='
Content-based Selection & Finding Similar Elements You can select elements by their text content in multiple ways, here's a full example on another website: >>> page = Fetcher().get('https://books.toscrape.com/index.html')
>>> page.find_by_text('Tipping the Velvet') # Find the first element whose text fully matches this text
>>> page.urljoin(page.find_by_text('Tipping the Velvet').attrib['href']) # We use `page.urljoin` to return the full URL from the relative `href`
'https://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html'
>>> page.find_by_text('Tipping the Velvet', first_match=False) # Get all matches if there are more
[]
>>> page.find_by_regex(r'£[\d\.]+') # Get the first element that its text content matches my price regex
£51.77' parent='
>>> page.find_by_regex(r'£[\d\.]+', first_match=False) # Get all elements that matches my price regex
[£51.77' parent=' ,
£53.74' parent=' ,
£50.10' parent=' ,
£47.82' parent=' ,
...]
Find all elements that are similar to the current element in location and attributes # For this case, ignore the 'title' attribute while matching
>>> page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title'])
[,
,
,
...]
# You will notice that the number of elements is 19 not 20 because the current element is not included.
>>> len(page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title']))
19
# Get the `href` attribute from all similar elements
>>> [element.attrib['href'] for element in page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title'])]
['catalogue/a-light-in-the-attic_1000/index.html',
'catalogue/soumission_998/index.html',
'catalogue/sharp-objects_997/index.html',
...]
To increase the complexity a little bit, let's say we want to get all books' data using that element as a starting point for some reason >>> for product in page.find_by_text('Tipping the Velvet').parent.parent.find_similar():
print({
"name": product.css_first('h3 a::text'),
"price": product.css_first('.price_color').re_first(r'[\d\.]+'),
"stock": product.css('.availability::text')[-1].clean()
})
{'name': 'A Light in the ...', 'price': '51.77', 'stock': 'In stock'}
{'name': 'Soumission', 'price': '50.10', 'stock': 'In stock'}
{'name': 'Sharp Objects', 'price': '47.82', 'stock': 'In stock'}
...
The documentation (https://github.com/D4Vinci/Scrapling/tree/main/docs/Examples) will provide more advanced examples. Handling Structural Changes Let's say you are scraping a page with a structure like this:
Product 1
Description 1
Product 2
Description 2
>>> quote.parent
...'>
>>> quote.parent.tag
'div'
>>> quote.children
["The...' parent=',
by ,
Tags: ]
>>> quote.siblings
[ ,
,
...]
>>> quote.next # gets the next element, the same logic applies to `quote.previous`
>>> quote.children.css_first(".author::text")
'Albert Einstein'
>>> quote.has_class('quote')
True
# Generate new selectors for any element
>>> quote.generate_css_selector
'body > div > div:nth-of-type(2) > div > div'
# Test these selectors on your favorite browser or reuse them again in the library's methods!
>>> quote.generate_xpath_selector
'//body/div/div[2]/div/div'
If your case needs more than the element's parent, you can iterate over the whole ancestors' tree of any element like below for ancestor in quote.iterancestors():
# do something with it...
You can search for a specific ancestor of an element that satisfies a function, all you need to do is to pass a function that takes an Adaptor object as an argument and return True if the condition satisfies or False otherwise like below: >>> quote.find_ancestor(lambda ancestor: ancestor.has_class('row'))
...' parent='
Content-based Selection & Finding Similar Elements You can select elements by their text content in multiple ways, here's a full example on another website: >>> page = Fetcher().get('https://books.toscrape.com/index.html')
>>> page.find_by_text('Tipping the Velvet') # Find the first element whose text fully matches this text
>>> page.urljoin(page.find_by_text('Tipping the Velvet').attrib['href']) # We use `page.urljoin` to return the full URL from the relative `href`
'https://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html'
>>> page.find_by_text('Tipping the Velvet', first_match=False) # Get all matches if there are more
[]
>>> page.find_by_regex(r'£[\d\.]+') # Get the first element that its text content matches my price regex
£51.77' parent='
>>> page.find_by_regex(r'£[\d\.]+', first_match=False) # Get all elements that matches my price regex
[£51.77' parent=' ,
£53.74' parent=' ,
£50.10' parent=' ,
£47.82' parent=' ,
...]
Find all elements that are similar to the current element in location and attributes # For this case, ignore the 'title' attribute while matching
>>> page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title'])
[,
,
,
...]
# You will notice that the number of elements is 19 not 20 because the current element is not included.
>>> len(page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title']))
19
# Get the `href` attribute from all similar elements
>>> [element.attrib['href'] for element in page.find_by_text('Tipping the Velvet').find_similar(ignore_attributes=['title'])]
['catalogue/a-light-in-the-attic_1000/index.html',
'catalogue/soumission_998/index.html',
'catalogue/sharp-objects_997/index.html',
...]
To increase the complexity a little bit, let's say we want to get all books' data using that element as a starting point for some reason >>> for product in page.find_by_text('Tipping the Velvet').parent.parent.find_similar():
print({
"name": product.css_first('h3 a::text'),
"price": product.css_first('.price_color').re_first(r'[\d\.]+'),
"stock": product.css('.availability::text')[-1].clean()
})
{'name': 'A Light in the ...', 'price': '51.77', 'stock': 'In stock'}
{'name': 'Soumission', 'price': '50.10', 'stock': 'In stock'}
{'name': 'Sharp Objects', 'price': '47.82', 'stock': 'In stock'}
...
The documentation (https://github.com/D4Vinci/Scrapling/tree/main/docs/Examples) will provide more advanced examples. Handling Structural Changes Let's say you are scraping a page with a structure like this:
Product 1
Description 1
Product 2
Description 2
>> old_url = "https://web.archive.org/web/20100102003420/http://stackoverflow.com/"
>> new_url = "https://stackoverflow.com/"
>>
>> page = Fetcher(automatch_domain='stackoverflow.com').get(old_url, timeout=30)
>> element1 = page.css_first(selector, auto_save=True)
>>
>> # Same selector but used in the updated website
>> page = Fetcher(automatch_domain="stackoverflow.com").get(new_url)
>> element2 = page.css_first(selector, auto_match=True)
>>
>> if element1.text == element2.text:
... print('Scrapling found the same element in the old design and the new design!')
'Scrapling found the same element in the old design and the new design!'
Note that I used a new argument called automatch_domain, this is because for Scrapling these are two different URLs, not the website so it isolates their data. To tell Scrapling they are the same website, we then pass the domain we want to use for saving auto-match data for them both so Scrapling doesn't isolate them. In a real-world scenario, the code will be the same except it will use the same URL for both requests so you won't need to use the automatch_domain argument. This is the closest example I can give to real-world cases so I hope it didn't confuse you :) Notes: 1. For the two examples above I used one time the Adaptor class and the second time the Fetcher class just to show you that you can create the Adaptor object by yourself if you have the source or fetch the source using any Fetcher class then it will create the Adaptor object for you. 2. Passing the auto_save argument with the auto_match argument set to False while initializing the Adaptor/Fetcher object will only result in ignoring the auto_save argument value and the following warning message text Argument `auto_save` will be ignored because `auto_match` wasn't enabled on initialization. Check docs for more info. This behavior is purely for performance reasons so the database gets created/connected only when you are planning to use the auto-matching features. Same case with the auto_match argument. The auto_match parameter works only for Adaptor instances not Adaptors so if you do something like this you will get an error python page.css('body').css('#p1', auto_match=True) because you can't auto-match a whole list, you have to be specific and do something like python page.css_first('body').css('#p1', auto_match=True) Find elements by filters Inspired by BeautifulSoup (https://www.kitploit.com/search/label/Beautifulsoup)'s find_all function you can find elements by using find_all/find methods. Both methods can take multiple types of filters and return all elements in the pages that all these filters apply to. To be more specific: Any string passed is considered a tag name Any iterable passed like List/Tuple/Set is considered an iterable of tag names. Any dictionary is considered a mapping of HTML element(s) attribute names and attribute values. Any regex patterns passed are used as filters to elements by their text content Any functions passed are used as filters Any keyword argument passed is considered as an HTML element attribute with its value. So the way it works is after collecting all passed arguments and keywords, each filter passes its results to the following filter in a waterfall-like filtering system.
It filters all elements in the current page/element in the following order: All elements with the passed tag name(s). All elements that match all passed attribute(s). All elements that its text content match all passed regex patterns. All elements that fulfill all passed function(s). Note: The filtering process always starts from the first filter it finds in the filtering order above so if no tag name(s) are passed but attributes are passed, the process starts from that layer and so on. But the order in which you pass the arguments doesn't matter. Examples to clear any confusion :) >> from scrapling.fetchers import Fetcher
>> new_url = "https://stackoverflow.com/"
>>
>> page = Fetcher(automatch_domain='stackoverflow.com').get(old_url, timeout=30)
>> element1 = page.css_first(selector, auto_save=True)
>>
>> # Same selector but used in the updated website
>> page = Fetcher(automatch_domain="stackoverflow.com").get(new_url)
>> element2 = page.css_first(selector, auto_match=True)
>>
>> if element1.text == element2.text:
... print('Scrapling found the same element in the old design and the new design!')
'Scrapling found the same element in the old design and the new design!'
Note that I used a new argument called automatch_domain, this is because for Scrapling these are two different URLs, not the website so it isolates their data. To tell Scrapling they are the same website, we then pass the domain we want to use for saving auto-match data for them both so Scrapling doesn't isolate them. In a real-world scenario, the code will be the same except it will use the same URL for both requests so you won't need to use the automatch_domain argument. This is the closest example I can give to real-world cases so I hope it didn't confuse you :) Notes: 1. For the two examples above I used one time the Adaptor class and the second time the Fetcher class just to show you that you can create the Adaptor object by yourself if you have the source or fetch the source using any Fetcher class then it will create the Adaptor object for you. 2. Passing the auto_save argument with the auto_match argument set to False while initializing the Adaptor/Fetcher object will only result in ignoring the auto_save argument value and the following warning message text Argument `auto_save` will be ignored because `auto_match` wasn't enabled on initialization. Check docs for more info. This behavior is purely for performance reasons so the database gets created/connected only when you are planning to use the auto-matching features. Same case with the auto_match argument. The auto_match parameter works only for Adaptor instances not Adaptors so if you do something like this you will get an error python page.css('body').css('#p1', auto_match=True) because you can't auto-match a whole list, you have to be specific and do something like python page.css_first('body').css('#p1', auto_match=True) Find elements by filters Inspired by BeautifulSoup (https://www.kitploit.com/search/label/Beautifulsoup)'s find_all function you can find elements by using find_all/find methods. Both methods can take multiple types of filters and return all elements in the pages that all these filters apply to. To be more specific: Any string passed is considered a tag name Any iterable passed like List/Tuple/Set is considered an iterable of tag names. Any dictionary is considered a mapping of HTML element(s) attribute names and attribute values. Any regex patterns passed are used as filters to elements by their text content Any functions passed are used as filters Any keyword argument passed is considered as an HTML element attribute with its value. So the way it works is after collecting all passed arguments and keywords, each filter passes its results to the following filter in a waterfall-like filtering system.
It filters all elements in the current page/element in the following order: All elements with the passed tag name(s). All elements that match all passed attribute(s). All elements that its text content match all passed regex patterns. All elements that fulfill all passed function(s). Note: The filtering process always starts from the first filter it finds in the filtering order above so if no tag name(s) are passed but attributes are passed, the process starts from that layer and so on. But the order in which you pass the arguments doesn't matter. Examples to clear any confusion :) >> from scrapling.fetchers import Fetcher