{"id":13472536,"url":"https://github.com/omkarcloud/botasaurus","last_synced_at":"2025-05-13T19:05:12.007Z","repository":{"id":163124546,"uuid":"638543018","full_name":"omkarcloud/botasaurus","owner":"omkarcloud","description":"The All in One Framework to Build Undefeatable Scrapers","archived":false,"fork":false,"pushed_at":"2025-04-26T11:17:17.000Z","size":65241,"stargazers_count":1839,"open_issues_count":19,"forks_count":162,"subscribers_count":18,"default_branch":"master","last_synced_at":"2025-04-26T13:57:41.321Z","etag":null,"topics":["anti-bot","anti-detect","anti-detect-browser","anti-detection","antidetect-browser","bot-detection","bypass-cloudflare","cloudflare-bypass","cloudflare-scrape","python-scraper","python-web-scraper","python-web-scraping","scraping-framework","scraping-python","scraping-tool","undetectable","undetected","undetected-chromedriver","web-crawling","web-scraping-python"],"latest_commit_sha":null,"homepage":"https://www.omkar.cloud/botasaurus/","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/omkarcloud.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":"CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":"AUTHORS","dei":null,"publiccode":null,"codemeta":null,"zenodo":null},"funding":{"github":["Chetan11-Dev"],"patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"lfx_crowdfunding":null,"custom":null}},"created_at":"2023-05-09T15:17:15.000Z","updated_at":"2025-04-26T11:17:20.000Z","dependencies_parsed_at":"2024-04-30T14:03:29.681Z","dependency_job_id":"b9059b3c-1d3d-49d8-a412-edb0f55a500e","html_url":"https://github.com/omkarcloud/botasaurus","commit_stats":{"total_commits":344,"total_committers":7,"mean_commits":"49.142857142857146","dds":0.3401162790697675,"last_synced_commit":"ccd7a8fde1c6235ddab50a77d716cb297d730ee6"},"previous_names":["omkarcloud/bose"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omkarcloud%2Fbotasaurus","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omkarcloud%2Fbotasaurus/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omkarcloud%2Fbotasaurus/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/omkarcloud%2Fbotasaurus/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/omkarcloud","download_url":"https://codeload.github.com/omkarcloud/botasaurus/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":250997552,"owners_count":21520255,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["anti-bot","anti-detect","anti-detect-browser","anti-detection","antidetect-browser","bot-detection","bypass-cloudflare","cloudflare-bypass","cloudflare-scrape","python-scraper","python-web-scraper","python-web-scraping","scraping-framework","scraping-python","scraping-tool","undetectable","undetected","undetected-chromedriver","web-crawling","web-scraping-python"],"created_at":"2024-07-31T16:00:55.541Z","updated_at":"2025-05-13T19:05:11.989Z","avatar_url":"https://github.com/omkarcloud.png","language":"Python","readme":"\u003cp align=\"center\"\u003e\r\n  \u003cimg src=\"https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/mascot.png\" alt=\"botasaurus\" /\u003e\r\n\u003c/p\u003e\r\n  \u003cdiv align=\"center\" style=\"margin-top: 0;\"\u003e\r\n  \u003ch1\u003e🤖 Botasaurus 🤖\u003c/h1\u003e\r\n  \u003c/div\u003e\r\n\r\n\u003ch3 align=\"center\"\u003e\r\n  The All in One Framework to Build Undefeatable Scrapers\r\n\u003c/h3\u003e\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003cb\u003eThe web has evolved. Finally, web scraping has too.\u003c/b\u003e\r\n\u003c/p\u003e\r\n\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003cimg src=\"https://views.whatilearened.today/views/github/omkarcloud/botasaurus.svg\" width=\"80px\" height=\"28px\" alt=\"View\" /\u003e\r\n\u003c/p\u003e\r\n\r\n\r\n\u003cp align=\"center\"\u003e\r\n  \u003ca href=\"https://gitpod.io/#https://github.com/omkarcloud/botasaurus-starter\"\u003e\r\n    \u003cimg alt=\"Run in Gitpod\" src=\"https://gitpod.io/button/open-in-gitpod.svg\" /\u003e\r\n  \u003c/a\u003e\r\n\u003c/p\u003e\r\n\r\n# IMPORTANT: We recently released an update with new features and bug fixes. Please run the following command to upgrade all Botasaurus packages to their latest versions:\r\n```shell\r\npython -m pip install bota botasaurus botasaurus-api botasaurus-requests botasaurus-driver botasaurus-proxy-authentication botasaurus-server botasaurus-humancursor --upgrade\r\n```\r\n\r\n## 🐿️ Botasaurus In a Nutshell\r\n\r\nHow wonderful that of all the web scraping tools out there, you chose to learn about Botasaurus. Congratulations! \r\n\r\nAnd now that you are here, you are in for an exciting, unusual, and rewarding journey that will make your web scraping life a lot easier.\r\n\r\nNow, let me tell you about Botasaurus in bullet points. (Because as per marketing gurus, YOU as a member of the Developer Tribe have a VERY short attention span.)\r\n\r\n*So, what is Botasaurus?*\r\n\r\nBotasaurus is an all-in-one web scraping framework that enables you to build awesome scrapers in less time, with less code, and with more fun.\r\n\r\nWe have put all our web scraping experience and best practices into Botasaurus to save you hundreds of hours of development time! \r\n\r\nNow, for the magical powers awaiting you after learning Botasaurus:\r\n\r\n- In terms of humaneness, what Superman is to Man, Botasaurus is to Selenium and Playwright. Easily pass every (Yes, E-V-E-R-Y) bot test, and build undetected scrapers.  \r\n    \r\nIn the video below, watch as we **bypass some of the best bot detection systems**:\r\n\r\n- ✅ [Cloudflare Web Application Firewall (WAF)](https://nopecha.com/demo/cloudflare)  \r\n- ✅ [BrowserScan Bot Detection](https://www.browserscan.net/bot-detection)  \r\n- ✅ [Fingerprint Bot Detection](https://fingerprint.com/products/bot-detection/)  \r\n- ✅ [Datadome Bot Detection](https://antoinevastel.com/bots/datadome)  \r\n- ✅ [Cloudflare Turnstile CAPTCHA](https://turnstile.zeroclover.io/)\r\n\r\n\u003cp align=\"center\"\u003e\r\n\u003cvideo src='https://github.com/user-attachments/assets/b4f6171f-f2a2-4255-9feb-2973ee9a25ae'/\u003e\r\n\u003c/p\u003e\r\n\r\n🔗 Want to try it yourself?\r\nSee the code behind these tests [here](https://github.com/omkarcloud/botasaurus/blob/master/bot_detection_tests.py)\r\n\r\n- Perform realistic, human-like mouse movements and say sayonara to detection\r\n![human-mode-demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/human-mode-demo.gif)\r\n\r\n- Convert your scraper into a desktop app for Mac, Windows, and Linux in 1 day, so not only developers but everyone can use your web scraper.\r\n\r\n![desktop-app-photo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/desktop-app-photo.png)\r\n\r\n- Turn your scraper into a beautiful website, making it easy for your customers to use it from anywhere, anytime.\r\n\r\n![pro-gmaps-demo](https://raw.githubusercontent.com/omkarcloud/google-maps-scraper/master/screenshots/demo.gif)\r\n\r\n- Save up to 97%, yes 97%, on browser proxy costs by using [browser-based fetch requests.](https://github.com/omkarcloud/botasaurus#how-to-significantly-reduce-proxy-costs-when-scraping-at-scale)\r\n\r\n- Easily save hours of development time with easy parallelization, profiles, extensions, and proxy configuration. Botasaurus makes asynchronous, parallel scraping child's play.\r\n\r\n- Use caching, sitemap, data cleaning, and other utilities to save hours of time spent writing and debugging code.\r\n\r\n- Easily scale your scraper to multiple machines with Kubernetes, and get your data faster than ever.\r\n\r\nAnd those are just the highlights. I mean! \r\n\r\nThere is so much more to Botasaurus that you will be amazed at how much time you will save with it.\r\n\r\n## 🚀 Getting Started with Botasaurus\r\n\r\nLet's dive right in with a straightforward example to understand Botasaurus.\r\n\r\nIn this example, we will go through the steps to scrape the heading text from [https://www.omkar.cloud/](https://www.omkar.cloud/).\r\n\r\n![Botasaurus in action](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-bot-running.gif)\r\n\r\n### Step 1: Install Botasaurus\r\n\r\nFirst things first, you need to install Botasaurus. Run the following command in your terminal:\r\n\r\n```shell\r\npython -m pip install --upgrade botasaurus\r\n```\r\n\r\n### Step 2: Set Up Your Botasaurus Project\r\n\r\nNext, let's set up the project:\r\n\r\n1. Create a directory for your Botasaurus project and navigate into it:\r\n\r\n```shell\r\nmkdir my-botasaurus-project\r\ncd my-botasaurus-project\r\ncode .  # This will open the project in VSCode if you have it installed\r\n```\r\n\r\n### Step 3: Write the Scraping Code\r\n\r\nNow, create a Python script named `main.py` in your project directory and paste the following code:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser\r\ndef scrape_heading_task(driver: Driver, data):\r\n    # Visit the Omkar Cloud website\r\n    driver.get(\"https://www.omkar.cloud/\")\r\n    \r\n    # Retrieve the heading element's text\r\n    heading = driver.get_text(\"h1\")\r\n\r\n    # Save the data as a JSON file in output/scrape_heading_task.json\r\n    return {\r\n        \"heading\": heading\r\n    }\r\n     \r\n# Initiate the web scraping task\r\nscrape_heading_task()\r\n```\r\n\r\nLet's understand this code:\r\n\r\n- We define a custom scraping task, `scrape_heading_task`, decorated with `@browser`:\r\n```python\r\n@browser\r\ndef scrape_heading_task(driver: Driver, data):\r\n```  \r\n\r\n- Botasaurus automatically provides a Humane Driver to our function:\r\n```python\r\ndef scrape_heading_task(driver: Driver, data):\r\n```  \r\n\r\n- Inside the function, we:\r\n    - Visit Omkar Cloud\r\n    - Extract the heading text\r\n    - Return the data to be automatically saved as `scrape_heading_task.json` by Botasaurus:\r\n```python\r\n    driver.get(\"https://www.omkar.cloud/\")\r\n    heading = driver.get_text(\"h1\")\r\n    return {\"heading\": heading}\r\n```  \r\n\r\n- Finally, we initiate the scraping task:\r\n```python\r\n# Initiate the web scraping task\r\nscrape_heading_task()\r\n```  \r\n\r\n### Step 4: Run the Scraping Task\r\n\r\nTime to run it:\r\n\r\n```shell\r\npython main.py\r\n```\r\n\r\nAfter executing the script, it will:\r\n- Launch Google Chrome\r\n- Visit [omkar.cloud](https://www.omkar.cloud/)\r\n- Extract the heading text\r\n- Save it automatically as `output/scrape_heading_task.json`.\r\n\r\n![Botasaurus in action](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-bot-running.gif)\r\n\r\nNow, let's explore another way to scrape the heading using the `request` module. Replace the previous code in `main.py` with the following:\r\n\r\n```python\r\nfrom botasaurus.request import request, Request\r\nfrom botasaurus.soupify import soupify\r\n\r\n@request\r\ndef scrape_heading_task(request: Request, data):\r\n    # Visit the Omkar Cloud website\r\n    response = request.get(\"https://www.omkar.cloud/\")\r\n\r\n    # Create a BeautifulSoup object    \r\n    soup = soupify(response)\r\n    \r\n    # Retrieve the heading element's text\r\n    heading = soup.find('h1').get_text()\r\n\r\n    # Save the data as a JSON file in output/scrape_heading_task.json\r\n    return {\r\n        \"heading\": heading\r\n    }     \r\n# Initiate the web scraping task\r\nscrape_heading_task()\r\n```\r\n\r\nIn this code:\r\n\r\n- We scrape the HTML using `request`, which is specifically designed for making browser-like humane requests.\r\n- Next, we parse the HTML into a `BeautifulSoup` object using `soupify()` and extract the heading.\r\n\r\n### Step 5: Run the Scraping Task (which makes Humane HTTP Requests)\r\n\r\nFinally, run it again:\r\n\r\n```shell\r\npython main.py\r\n```\r\n\r\nThis time, you will observe the exact same result as before, but instead of opening a whole browser, we are making browser-like humane HTTP requests.\r\n\r\n## 💡 Understanding Botasaurus\r\n\r\n### What is Botasaurus Driver, and why should I use it over Selenium and Playwright?\r\n\r\nBotasaurus Driver is a web automation driver like Selenium, and the single most important reason to use it is because it is truly humane. You will not, and I repeat NOT, have any issues accessing any website.\r\n\r\nPlus, it is super fast to launch and use, and the API is designed by and for web scrapers, and you will love it.\r\n\r\n### How do I access Cloudflare-protected pages using Botasaurus?\r\n\r\nCloudflare is the most popular protection system on the web. So, let's see how Botasaurus can help you solve various Cloudflare challenges.\r\n\r\n**Connection Challenge**\r\n\r\nThis is the single most popular challenge and requires making a browser-like connection with appropriate headers. It's commonly used for:\r\n- Product Pages\r\n- Blog Pages \r\n- Search Result Pages\r\n\r\n\u003c!-- Example Page: https://www.g2.com/products/github/reviews --\u003e\r\n\r\n#### What Works?\r\n\r\n- Visiting the website via Google Referrer (which makes it seem as if the user has arrived from a Google search).\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser\r\ndef scrape_heading_task(driver: Driver, data):\r\n    # Visit the website via Google Referrer\r\n    driver.google_get(\"https://www.cloudflare.com/en-in/\")\r\n    driver.prompt()\r\n    heading = driver.get_text('h1')\r\n    return heading\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n- Use the request module. The Request Object is smart and, by default, visits any link with a Google Referrer. Although it works, you will need to use retries.\r\n\r\n```python\r\nfrom botasaurus.request import request, Request\r\n\r\n@request(max_retry=10)\r\ndef scrape_heading_task(request: Request, data):\r\n    response = request.get(\"https://www.cloudflare.com/en-in/\")\r\n    print(response.status_code)\r\n    response.raise_for_status()\r\n    return response.text\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n**JS with Captcha Challenge**\r\n\r\nThis challenge requires performing JS computations that differentiate a Chrome controlled by Selenium/Puppeteer/Playwright from a real Chrome. It also involves solving a Captcha. It's used to for pages which are rarely but sometimes visited by people, like:\r\n- 5th Review page\r\n- Auth pages\r\n\r\nExample Page: https://nopecha.com/demo/cloudflare\r\n\r\n#### What Does Not Work?\r\nUsing `@request` does not work because although it can make browser-like HTTP requests, it cannot run JavaScript to solve the challenge.\r\n\r\n#### What Works?\r\nPass the `bypass_cloudflare=True` argument to the `google_get` method.\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser\r\ndef scrape_heading_task(driver: Driver, data):\r\n    driver.google_get(\"https://nopecha.com/demo/cloudflare\", bypass_cloudflare=True)\r\n    driver.prompt()\r\n\r\nscrape_heading_task()\r\n```\r\n![Cloudflare JS with Captcha Challenge Demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/cloudflare-js-captcha-demo.gif)\r\n\r\n### What are the benefits of a UI scraper?\r\n\r\nHere are some benefits of creating a scraper with a user interface:\r\n\r\n- Simplify your scraper usage for customers, eliminating the need to teach them how to modify and run your code.\r\n- Protect your code by hosting the scraper on the web and offering a monthly subscription, rather than providing full access to your code. This approach:\r\n  - Safeguards your Python code from being copied and reused, increasing your customer's lifetime value.\r\n  - Generate monthly recurring revenue via subscription from your customers, surpassing a one-time payment.\r\n- Enable sorting, filtering, and downloading of data in various formats (JSON, Excel, CSV, etc.).\r\n- Provide access via a REST API for seamless integration.\r\n- Create a polished frontend, backend, and API integration with minimal code.\r\n\r\n### How to run a UI-based scraper?\r\n\r\nLet's run the Botasaurus Starter Template (the recommended template for greenfield Botasaurus projects), which scrapes the heading of the provided link by following these steps:\r\n\r\n1. Clone the Starter Template:\r\n   ```\r\n   git clone https://github.com/omkarcloud/botasaurus-starter my-botasaurus-project\r\n   cd my-botasaurus-project\r\n   ```\r\n\r\n2. Install dependencies (will take a few minutes):\r\n   ```\r\n   python -m pip install -r requirements.txt\r\n   python run.py install\r\n   ```\r\n\r\n3. Run the scraper:\r\n   ```\r\n   python run.py\r\n   ```\r\n\r\nYour browser will automatically open up at http://localhost:3000/. Then, enter the link you want to scrape (e.g., https://www.omkar.cloud/) and click on the Run Button.\r\n\r\n![starter-scraper-demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-scraper-demo.gif)\r\n\r\nAfter some seconds, the data will be scraped.\r\n![starter-scraper-demo-result](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-scraper-demo-result.png)\r\n\r\nVisit http://localhost:3000/output to see all the tasks you have started.\r\n\r\n![starter-scraper-demo-tasks](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-scraper-demo-tasks.png)\r\n\r\nGo to http://localhost:3000/about to see the rendered README.md file of the project.\r\n\r\n![starter-scraper-demo-readme](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-scraper-demo-readme.png)\r\n\r\nFinally, visit http://localhost:3000/api-integration to see how to access the scraper via API.\r\n\r\n![starter-scraper-demo-api](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-scraper-demo-api.png)\r\n\r\nThe API documentation is generated dynamically based on your scraper's inputs, sorts, filters, etc., and is unique to your scraper. \r\n\r\nSo, whenever you need to run the scraper via API, visit this tab and copy the code specific to your scraper.\r\n\r\n### How to create a UI scraper using Botasaurus?\r\n\r\nCreating a UI scraper with Botasaurus is a simple 3-step process:\r\n1. Create your scraper function\r\n2. Add the scraper to the server using 1 line of code \r\n3. Define the input controls for the scraper\r\n\r\nTo understand these steps, let's go through the code of the Botasaurus Starter Template that you just ran.\r\n\r\n#### Step 1: Create the Scraper Function\r\n\r\nIn `src/scrape_heading_task.py`, we define a scraping function that basically does the following:\r\n\r\n1. Receives a `data` object and extracts the \"link\".\r\n2. Retrieves the HTML content of the webpage using the \"link\".\r\n3. Converts the HTML into a BeautifulSoup object.\r\n4. Locates the heading element, extracts its text content, and returns it.\r\n\r\n```python\r\nfrom botasaurus.request import request, Request\r\nfrom botasaurus.soupify import soupify\r\n\r\n@request\r\ndef scrape_heading_task(request: Request, data):\r\n    # Visit the Link\r\n    response = request.get(data[\"link\"])\r\n\r\n    # Create a BeautifulSoup object    \r\n    soup = soupify(response)\r\n    \r\n    # Retrieve the heading element's text\r\n    heading = soup.find('h1').get_text()\r\n\r\n    # Save the data as a JSON file in output/scrape_heading_task.json\r\n    return {\r\n        \"heading\": heading\r\n    }\r\n```\r\n\r\n#### Step 2: Add the Scraper to the Server\r\n\r\nIn `backend/scrapers.py`, we:\r\n- Import our scraping function\r\n- Use `Server.add_scraper()` to register the scraper\r\n\r\n```python\r\nfrom botasaurus_server.server import Server\r\nfrom src.scrape_heading_task import scrape_heading_task\r\n\r\n# Add the scraper to the server\r\nServer.add_scraper(scrape_heading_task)\r\n```\r\n\r\n#### Step 3: Define the Input Controls\r\n\r\nIn `backend/inputs/scrape_heading_task.js`, we:\r\n- Define a `getInput` function that takes the controls parameter\r\n- Add a link input control to it\r\n- Use JSDoc comments to enable IntelliSense Code Completion in VSCode as you won't be able to remember all the controls in botasaurus.\r\n\r\n\r\n```js\r\n/**\r\n * @typedef {import('../../frontend/node_modules/botasaurus-controls/dist/index').Controls} Controls\r\n */\r\n\r\n/**\r\n * @param {Controls} controls\r\n */\r\nfunction getInput(controls) {\r\n    controls\r\n        // Render a Link Input, which is required, defaults to \"https://stackoverflow.blog/open-source\". \r\n        .link('link', { isRequired: true, defaultValue: \"https://stackoverflow.blog/open-source\" })\r\n}\r\n```\r\n\r\nAbove was a simple example; below is a real-world example with multi-text, number, switch, select, section, and other controls.\r\n\r\n```js\r\n/**\r\n * @typedef {import('../../frontend/node_modules/botasaurus-controls/dist/index').Controls} Controls\r\n */\r\n\r\n\r\n/**\r\n * @param {Controls} controls\r\n */\r\nfunction getInput(controls) {\r\n    controls\r\n        .listOfTexts('queries', {\r\n            defaultValue: [\"Web Developers in Bangalore\"],\r\n            placeholder: \"Web Developers in Bangalore\",\r\n            label: 'Search Queries',\r\n            isRequired: true\r\n        })\r\n        .section(\"Email and Social Links Extraction\", (section) =\u003e {\r\n            section.text('api_key', {\r\n                placeholder: \"2e5d346ap4db8mce4fj7fc112s9h26s61e1192b6a526af51n9\",\r\n                label: 'Email and Social Links Extraction API Key',\r\n                helpText: 'Enter your API key to extract email addresses and social media links.',\r\n            })\r\n        })\r\n        .section(\"Reviews Extraction\", (section) =\u003e {\r\n            section\r\n                .switch('enable_reviews_extraction', {\r\n                    label: \"Enable Reviews Extraction\"\r\n                })\r\n                .numberGreaterThanOrEqualToZero('max_reviews', {\r\n                    label: 'Max Reviews per Place (Leave empty to extract all reviews)',\r\n                    placeholder: 20,\r\n                    isShown: (data) =\u003e data['enable_reviews_extraction'], defaultValue: 20,\r\n                })\r\n                .choose('reviews_sort', {\r\n                    label: \"Sort Reviews By\",\r\n                    isRequired: true, isShown: (data) =\u003e data['enable_reviews_extraction'], defaultValue: 'newest', options: [{ value: 'newest', label: 'Newest' }, { value: 'most_relevant', label: 'Most Relevant' }, { value: 'highest_rating', label: 'Highest Rating' }, { value: 'lowest_rating', label: 'Lowest Rating' }]\r\n                })\r\n        })\r\n        .section(\"Language and Max Results\", (section) =\u003e {\r\n            section\r\n                .addLangSelect()\r\n                .numberGreaterThanOrEqualToOne('max_results', {\r\n                    placeholder: 100,\r\n                    label: 'Max Results per Search Query (Leave empty to extract all places)'\r\n                })\r\n        })\r\n        .section(\"Geo Location\", (section) =\u003e {\r\n            section\r\n                .text('coordinates', {\r\n                    placeholder: '12.900490, 77.571466'\r\n                })\r\n                .numberGreaterThanOrEqualToOne('zoom_level', {\r\n                    label: 'Zoom Level (1-21)',\r\n                    defaultValue: 14,\r\n                    placeholder: 14\r\n                })\r\n        })\r\n}\r\n```\r\n\r\nI encourage you to paste the above code into `backend/inputs/scrape_heading_task.js` and reload the page, and you will see a complex set of input controls like the image shown.\r\n\r\n![complex-input](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/complex-input.png)\r\n\r\nNow, to use the Botasaurus UI for adding new scrapers, remember these points:\r\n\r\n1. Create a `backend/inputs/{your_scraping_function_name}.js` file for each scraping function.\r\n2. Define the `getInput` function in the file with the necessary controls.\r\n3. Use JSDoc comments to enable IntelliSense code completion in VSCode, as you won't be able to remember all the controls in Botasaurus.\r\n\r\nUse this template as a starting point for new scraping function's input controls js file:\r\n\r\n```js\r\n/**\r\n * @typedef {import('../../frontend/node_modules/botasaurus-controls/dist/index').Controls} Controls\r\n */\r\n\r\n/**\r\n * @param {Controls} controls\r\n */\r\nfunction getInput(controls) {\r\n    // Define your controls here.\r\n}\r\n```\r\n\r\nThat's it! With these simple steps, you can create a fully functional UI scraper using Botasaurus.\r\n\r\nLater, you will learn how to add sorts and filters to make your UI scraper even more powerful and user-friendly.\r\n\r\n![sorts-filters](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/sorts-filters.png)\r\n\r\n### What is a Desktop Extractor?\r\nA **Desktop Extractor** is a standalone application that runs on your computer and extracts specific data from websites, PDFs, Excel files, and other documents. Unlike web-based tools, desktop extractors run locally, giving **faster performance** and **zero cloud costs**.\r\n\r\n![Desktop Extractor showing an application interface with extraction options](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/desktop-app-photo.png)\r\n\r\n### What advantages do Desktop Scrapers have over web-based scrapers?\r\n**Desktop Scrapers** offer key advantages over web-based scraper solutions like Outscraper:  \r\n\r\n- **Zero Infrastructure Costs**: \r\n   - Runs on the user's machine, eliminating expensive cloud computing fees.  \r\n   - Lower cloud costs allow you to offer lower pricing, attracting more customers and increasing revenue.\r\n\r\n- **Faster Execution**:  \r\n  - Instant execution, no delays for cloud resource allocation.  \r\n  - Uses the user's system, which is much faster than shared cloud servers.  \r\n\r\n- **Increased Customer Engagement**:  \r\n  The app sits right on the user's desktop, encouraging frequent use compared to web tools they must actively visit via browser.  \r\n\r\n- **Cross-Platform Deployment in 1 Day**:  \r\n  With **Botasaurus**, you can launch a desktop scraper for **Windows, macOS, and Linux** within a day. No need to build a website, manage servers, or handle scaling issues. Bota Desktop includes built-in features such as:\r\n  - Task management\r\n  - Data Table\r\n  - Data export (Excel, CSV, etc.)\r\n  - Sorting \u0026 Filtering\r\n  - Caching and many more\r\n\r\nWith zero usage costs, faster performance, and easier development, Desktop Scrapers outperform web-based alternatives.\r\n\r\n### How to Build a Desktop Extractor\r\nCreating Desktop Extractors is easier than you think! All you need is a basic understanding of JavaScript. Once you're ready, read the [Desktop Extraction Tutorial](https://github.com/omkarcloud/botasaurus/blob/master/botasaurus-desktop-tutorial.md#what-will-be-built-and-why), where we'll guide you through building two practical extractors:\r\n- **Yahoo Finance Stock Scraper** – Extracts real-time stock prices from Yahoo Finance.\r\n\r\n![Stock Scraper Demo showing the application extracting stock prices from Yahoo Finance](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/stock-scraper-preview.gif) \r\n\r\n- **Amazon Invoice PDF Extractor** – Automates the extraction of key invoice data like Document Number, Document Date, and Place of Supply from Amazon PDFs.  \r\n\r\n![PDF Extraction Demo showing the application extracting data from Amazon PDF invoices](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/pdf-extract-preview.gif)  \r\n\r\nAs a web scraper, you might naturally want to focus on web scraping. Still, I want you to create the **Amazon Invoice PDF Extractor** project. Why? Because many developers overlook the immense potential of extracting data from PDFs, Excel files, and other documents.  \r\n\r\n**Document Data Extraction is a large untapped market.** For example, even in most developed countries, accountants often spend hundreds of hours manually entering invoice data for tax filings. A desktop extractor can transform this tedious, error-prone process into a task that takes just minutes, delivering 100% accurate results.\r\n\r\nPlease read the step-by-step tutorial [here](https://github.com/omkarcloud/botasaurus/blob/master/botasaurus-desktop-tutorial.md#what-will-be-built-and-why). By the end of this short guide, you'll be able to create powerful desktop extractors in very little time.\r\n\r\n### What is Botasaurus, and what are its main features?\r\n\r\nBotasaurus is an all-in-one web scraping framework designed to achieve three main goals:  \r\n\r\n1. Provide essential web scraping utilities to streamline the scraping process.\r\n\r\nTo accomplish these goals, Botasaurus gives you 3 decorators:\r\n- `@browser`: For scraping web pages using a humane browser.\r\n- `@request`: For scraping web pages using lightweight and humane HTTP requests.\r\n- `@task`: \r\n  - For scraping web pages using third-party libraries like `playwright` or `selenium`.\r\n  - or, For running non-web scraping tasks, such as data processing (e.g., converting video to audio). Botasaurus is not limited to web scraping tasks; any Python function can be made accessible with a stunning UI and user-friendly API.\r\n\r\nIn practice, while developing with Botasaurus, you will spend most of your time in the following areas:\r\n- Configuring your scrapers via decorators with settings like:\r\n  - Which proxy to use\r\n  - How many scrapers to run in parallel, etc.\r\n- Writing your core web scraping logic using BeautifulSoup (bs4) or the Botasaurus Driver.\r\n\r\nAdditionally, you will utilize the following Botasaurus utilities for debugging and development:\r\n- `bt`: Mainly for writing JSON, EXCEL, and HTML temporary files, and for data cleaning.\r\n- `Sitemap`: For accessing the website's links and sitemap.\r\n- Minor utilities like:\r\n  - `LocalStorage`: For storing scraper state.\r\n  - `soupify`: For creating BeautifulSoup objects from Driver, Requests response, Driver Element, or HTML string.\r\n  - `IPUtils`: For obtaining information (IP, country, etc.) about the current IP address.\r\n  - `Cache`: For managing the cache.\r\n\r\nBy simply configuring these three decorators (`@browser`, `@request`, and `@task`) with arguments, you can easily create `real-time scrapers` and `large-scale datasets`, thus saving you countless hours that would otherwise be spent writing and debugging code from scratch.\r\n\r\n2. Offering a Python-based UI scraper that allows non-technical users to run scrapers online by simply visiting a website link. (As described in the previous FAQ)  \r\n\r\n3. Make it easy to create desktop applications for Mac, Windows, and Linux, using JavaScript. More details can be found in the [Botasaurus Desktop Documentation here](https://github.com/omkarcloud/botasaurus/blob/master/botasaurus-desktop-tutorial.md).\r\n\r\n### How to use decorators in Botasaurus?\r\n\r\nDecorators are the heart of Botasaurus. To use a decorator function, you can call it with:\r\n- A single item\r\n- A list of items\r\n\r\nIf a scraping function is given a list of items, it will sequentially call the scraping function for each data item.\r\n\r\nFor example, if you pass a list of three links to the `scrape_heading_task` function:\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser\r\ndef scrape_heading_task(driver: Driver, link):\r\n    driver.get(link)\r\n    heading = driver.get_text(\"h1\")\r\n    return heading\r\n\r\nscrape_heading_task([\"https://www.omkar.cloud/\", \"https://www.omkar.cloud/blog/\", \"https://stackoverflow.com/\"]) # \u003c-- list of items\r\n```\r\n\r\nThen, Botasaurus will launch a new browser instance for each item, and the final results will be stored in `output/scrape_heading_task.json`.\r\n\r\n![list-demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/list-demo.gif)\r\n\r\n### How does Botasaurus help me in debugging?\r\n\r\nBotasaurus helps you in debugging by:\r\n\r\n- Easily viewing the result of the scraping function, as it is saved in `output/{your_scraping_function_name}.json`. Say goodbye to print statements.\r\n\r\n![scraped data](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/scraped-data.png)\r\n\r\n- Bringing your attention to errors in browser mode with a beep sound and pausing the browser, allowing you to debug the error on the spot.\r\n\r\n![](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/error-prompt.png)\r\n\r\n- Even if an exception is raised in headless mode, it will still open the website in your default browser, making it easier to debug code in a headless browser. (Isn't it cool?)\r\n\r\n![headless-error](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/headless-error.png)\r\n\r\n### How to configure the Browser Decorator?\r\n\r\nThe Browser Decorator allows you to easily configure various aspects of the browser, such as:\r\n\r\n- Blocking images and CSS\r\n- Setting up proxies\r\n- Specifying profiles\r\n- Enabling headless mode\r\n- Using Chrome extensions\r\n- Captcha Solving\r\n- Selecting language\r\n- Passing Arguments to Chrome\r\n\r\n#### Blocking Images and CSS\r\n\r\nBlocking images is one of the most important configurations when scraping at scale. Blocking images can significantly: \r\n- Speed up your web scraping tasks\r\n- Reduce bandwidth usage\r\n- And save money on proxies. (Best of All!)\r\n\r\nFor example, a page that originally takes 4 seconds and 12 MB to load might only take one second and 100 KB after blocking images and CSS.\r\n\r\nTo block images, use the `block_images` parameter:\r\n\r\n```python\r\n@browser(\r\n    block_images=True,\r\n)\r\n```\r\n\r\nTo block both images and CSS, use `block_images_and_css`:\r\n\r\n```python\r\n@browser(\r\n    block_images_and_css=True,\r\n)    \r\n```\r\n\r\n#### Proxies\r\n\r\nTo use proxies, simply specify the `proxy` parameter:\r\n\r\n```python\r\n@browser(\r\n    proxy=\"http://username:password@proxy-provider-domain:port\"\r\n)    \r\ndef visit_what_is_my_ip(driver: Driver, data):\r\n    driver.get(\"https://whatismyipaddress.com/\")\r\n    driver.prompt()\r\n\r\nvisit_what_is_my_ip()\r\n```\r\n\r\nYou can also pass a list of proxies, and the proxy will be automatically rotated:\r\n\r\n```python\r\n@browser(\r\n    proxy=[\r\n        \"http://username:password@proxy-provider-domain:port\", \r\n        \"http://username2:password2@proxy-provider-domain:port\"\r\n    ]\r\n)\r\ndef visit_what_is_my_ip(driver: Driver, data):\r\n    driver.get(\"https://whatismyipaddress.com/\")\r\n    driver.prompt()\r\n\r\nvisit_what_is_my_ip() \r\n```\r\n\r\n#### Profile\r\n\r\nEasily specify the Chrome profile using the `profile` option:\r\n\r\n```python\r\n@browser(\r\n    profile=\"pikachu\"\r\n)    \r\n```\r\n\r\nHowever, each Chrome profile can become very large (e.g., 100 MB) and can eat up all your computer storage. \r\n\r\nTo solve this problem, use the `tiny_profile` option, which is a lightweight alternative to Chrome profiles. \r\n\r\nWhen creating hundreds of Chrome profiles, it is highly recommended to use the `tiny_profile` option because:\r\n\r\n- Creating 1000 Chrome profiles will take at least 100 GB, whereas 1000 tiny profiles will take up only 1 MB of storage, making tiny profiles easy to store and back up.\r\n- Tiny profiles are cross-platform, meaning you can create profiles on a Linux server, copy the `./profiles` folder to a Windows PC, and easily run them.\r\n\r\nUnder the hood, tiny profiles persist cookies from visited websites, making them extremely lightweight (around 1 KB) while providing the same session persistence.\r\n\r\nHere's how to use the tiny profile:\r\n\r\n```python\r\n@browser(\r\n    tiny_profile=True, \r\n    profile=\"pikachu\",\r\n)    \r\n```\r\n\r\n#### Headless Mode\r\n\r\nEnable headless mode with `headless=True`:\r\n```python\r\n@browser(\r\n    headless=True\r\n)    \r\n```\r\nNote that if you use headless mode, you will surely be identified by services like Cloudflare and Datadome. Therefore, use headless mode only when scraping websites that don't use such services.\r\n\r\n#### Chrome Extensions\r\n\r\nBotasaurus allows the use of ANY Chrome Extension with just 1 line of code. The example below shows how to use the Mouse Coordinates Chrome Extension to show current mouse X and Y coordinates on web pages:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom chrome_extension_python import Extension\r\n\r\n@browser(\r\n    extensions=[\r\n        Extension(\r\n            \"https://chromewebstore.google.com/detail/mouse-coordinates/mfohnjojhopfcahiddmeljeholnciakl\"\r\n        )\r\n    ],\r\n)\r\ndef scrape_while_blocking_ads(driver: Driver, data):\r\n    driver.get(\"https://example.com/\")\r\n    driver.prompt()\r\n\r\nscrape_while_blocking_ads()\r\n```\r\n\r\nIn some cases, an extension may require additional configuration, such as API keys or credentials. For such scenarios, you can create a custom extension. Learn more about creating and configuring custom extensions [here](https://github.com/omkarcloud/chrome-extension-python).\r\n\r\n#### Captcha Solving\r\n\r\nEncountering captchas is common in web scraping. You can use the [capsolver_extension_python](https://github.com/omkarcloud/capsolver-extension-python?tab=readme-ov-file#installation) package to automatically solve CAPTCHAs with Capsolver.  \r\n\r\nTo use it, first install the package:  \r\n\r\n```bash\r\npython -m pip install capsolver_extension_python\r\n```\r\n\r\nThen, integrate it into your code as follows:  \r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom capsolver_extension_python import Capsolver\r\n\r\n# Replace \"CAP-MY_KEY\" with your actual CapSolver API key\r\n@browser(extensions=[Capsolver(api_key=\"CAP-MY_KEY\")])  \r\ndef solve_captcha(driver: Driver, data):\r\n    driver.get(\"https://recaptcha-demo.appspot.com/recaptcha-v2-checkbox.php\")\r\n    driver.prompt()\r\n\r\nsolve_captcha()\r\n```  \r\n\r\n#### Language\r\n\r\nSpecify the language using the `lang` option:\r\n\r\n```python\r\nfrom botasaurus.lang import Lang\r\n\r\n@browser(\r\n    lang=Lang.Hindi,\r\n)\r\n```\r\n\r\n#### User Agent and Window Size\r\n\r\nTo make the browser really humane, Botasaurus does not change browser fingerprints by default, because using fingerprints makes the browser easily identifiable by running CSS tests to find mismatches between the provided user agent and the actual user agent.\r\n\r\nHowever, if you need fingerprinting, use the `user_agent` and `window_size` options:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom botasaurus.user_agent import UserAgent\r\nfrom botasaurus.window_size import WindowSize\r\n\r\n@browser(\r\n    user_agent=UserAgent.RANDOM,\r\n    window_size=WindowSize.RANDOM,\r\n)\r\ndef visit_whatsmyua(driver: Driver, data):\r\n    driver.get(\"https://www.whatsmyua.info/\")\r\n    driver.prompt()\r\n\r\nvisit_whatsmyua()\r\n```\r\n\r\nWhen working with profiles, you want the fingerprints to remain consistent. You don't want the user's user agent to be Chrome 106 on the first visit and then become Chrome 102 on the second visit. \r\n\r\nSo, when using profiles, use the `HASHED` option to generate a consistent user agent and window size based on the profile's hash:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom botasaurus.user_agent import UserAgent\r\nfrom botasaurus.window_size import WindowSize\r\n\r\n@browser(\r\n    profile=\"pikachu\",\r\n    user_agent=UserAgent.HASHED,\r\n    window_size=WindowSize.HASHED,\r\n)\r\ndef visit_whatsmyua(driver: Driver, data):\r\n    driver.get(\"https://www.whatsmyua.info/\")\r\n    driver.prompt()\r\n    \r\nvisit_whatsmyua()\r\n\r\n# Everytime Same UserAgent and WindowSize\r\nvisit_whatsmyua()\r\n```\r\n#### Passing Arguments to Chrome\r\n\r\nTo pass arguments to Chrome, use the `add_arguments` option:\r\n\r\n```python\r\n@browser(\r\n    add_arguments=['--headless=new'],\r\n)\r\n```\r\n\r\nTo dynamically generate arguments based on the `data` parameter, pass a function:\r\n\r\n```python\r\ndef get_arguments(data):\r\n    return ['--headless=new']\r\n\r\n@browser(\r\n    add_arguments=get_arguments,\r\n)\r\n```\r\n\r\n#### Wait for Complete Page Load\r\n\r\nBy default, Botasaurus waits for all page resources (DOM, JavaScript, CSS, images, etc.) to load before calling your scraping function with the driver.\r\n\r\nHowever, sometimes the DOM is ready, but JavaScript, images, etc., take forever to load. \r\n\r\nIn such cases, you can set `wait_for_complete_page_load` to `False` to interact with the DOM as soon as the HTML is parsed and the DOM is ready:\r\n\r\n```python\r\n@browser(\r\n    wait_for_complete_page_load=False,\r\n)\r\n```\r\n\r\n#### Reuse Driver\r\n\r\nConsider the following example:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser\r\ndef scrape_data(driver: Driver, link):\r\n    driver.get(link)\r\n\r\nscrape_data([\"https://www.omkar.cloud/\", \"https://www.omkar.cloud/blog/\", \"https://stackoverflow.com/\"])\r\n```\r\n\r\nIf you run this code, the browser will be recreated on each page visit, which is inefficient. \r\n\r\n![list-demo-omkar](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/list-demo.gif)\r\n\r\nTo solve this problem, use the `reuse_driver` option which is great for cases like:\r\n- Scraping a large number of links and reusing the same browser instance for all page visits.\r\n- Running your scraper in a cloud server to scrape data on demand, without recreating Chrome on each request.\r\n\r\nHere's how to use `reuse_driver` which will reuse the same Chrome instance for visiting each link.\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(\r\n    reuse_driver=True\r\n)\r\ndef scrape_data(driver: Driver, link):\r\n    driver.get(link)\r\n\r\nscrape_data([\"https://www.omkar.cloud/\", \"https://www.omkar.cloud/blog/\", \"https://stackoverflow.com/\"])\r\n```\r\n**Result**\r\n![list-demo-reuse-driver.gif](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/list-demo-reuse-driver.gif)\r\n \r\n---\r\n\r\nAlso, by default, whenever the program ends or is canceled, Botasaurus smartly closes any open Chrome instances, leaving no instances running in the background.\r\n\r\nIn rare cases, you may want to explicitly close the Chrome instance. For such scenarios, you can use the `.close()` method on the scraping function:\r\n\r\n```python\r\nscrape_data.close()\r\n```\r\n\r\nThis will close any Chrome instances that remain open after the scraping function ends.\r\n\r\n\r\n### How to Significantly Reduce Proxy Costs When Scraping at Scale?\r\n\r\nRecently, we had a project requiring access to around 100,000 pages from a well-protected website, necessitating the use of Residential Proxies.\r\n\r\nEven after blocking images, we still required 250GB of proxy bandwidth, costing approximately $1050 (at $4.2 per GB with IP Royal). \r\n\r\nThis was beyond our budget :(\r\n\r\nTo solve this, we implemented a smart strategy:\r\n- We first visited the website normally.\r\n- We then made requests for subsequent pages using the browser's `fetch` API.\r\n\r\nSince we were only requesting the HTML, which was well compressed by the browser, we reduced our proxy bandwidth needs to just 5GB, costing only $30.\r\n\r\nThis resulted in savings of around $1000!\r\n\r\nHere's an example of how you can do something similar in Botasaurus:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom botasaurus.soupify import soupify\r\n\r\n@browser(\r\n    reuse_driver=True,  # Reuse the browser\r\n    max_retry=5,        # Retry up to 5 times on failure\r\n)\r\ndef scrape_data(driver: Driver, link):\r\n    # If the browser is newly opened, first visit the link\r\n    if driver.config.is_new:\r\n        driver.google_get(link)\r\n    \r\n    # Make requests using the browser fetch API\r\n    response = driver.requests.get(link)\r\n    response.raise_for_status()  # Ensure the request was successful\r\n    html = response.text\r\n\r\n    # Parse the HTML to extract the desired data\r\n    soup = soupify(html)\r\n    stock_name = soup.select_one('[data-testid=\"quote-hdr\"] h1').get_text()\r\n    stock_price = soup.select_one('[data-testid=\"qsp-price\"]').get_text()\r\n    \r\n    return {\r\n        \"stock_name\": stock_name,\r\n        \"stock_price\": stock_price,\r\n    }\r\n\r\n# List of URLs to scrape\r\nlinks = [\r\n    \"https://finance.yahoo.com/quote/AAPL/\",\r\n    \"https://finance.yahoo.com/quote/GOOG/\",\r\n    \"https://finance.yahoo.com/quote/MSFT/\",\r\n]\r\n\r\n# Execute the scraping function for the list of links\r\nscrape_data(links)\r\n```\r\n\r\nNote:\r\n1. **Dealing with 429 (Too Many Requests) Errors**\r\n\r\n   If you encounter a 429 error, add a delay before making another request. Most websites using Nginx, setting a rate limit of 1 request per second. To respect this limit, a delay of 1.13 seconds is recommended.\r\n\r\n   ```python\r\n   driver.sleep(1.13)  # Delay to respect the rate limit\r\n   response = driver.requests.get(link)\r\n   ```\r\n\r\n2. **Handling 400 Errors Due to Large Cookies**\r\n\r\n   If you encounter a 400 error with a \"cookie too large\" message, delete the cookies and retry the request.\r\n\r\n   ```python\r\n   response = driver.requests.get(link)\r\n\r\n   if response.status_code == 400:\r\n       driver.delete_cookies()  # Delete cookies to resolve the error\r\n       driver.short_random_sleep()  # Short delay before retrying\r\n       response = driver.requests.get(link)\r\n   ```\r\n3. You can also use `driver.requests.get_mank(links)` to make multiple requests in parallel, which is faster than making them sequentially.\r\n\r\n### How to Configure the Browser's Chrome Profile, Language, and Proxy Dynamically Based on Data Parameters?\r\n\r\nThe decorators in Botasaurus are really flexible, allowing you to pass a function that can derive the browser configuration based on the data item parameter. This is particularly useful when working with multiple Chrome profiles.\r\n\r\nYou can dynamically configure the browser's Chrome profile and proxy using decorators in two ways:\r\n\r\n1. Using functions to extract configuration values from data:\r\n   - Define functions to extract the desired configuration values from the `data` parameter.\r\n   - Pass these functions as arguments to the `@browser` decorator.\r\n\r\n   Example:\r\n   ```python\r\n   from botasaurus.browser import browser, Driver\r\n\r\n   def get_profile(data):\r\n       return data[\"profile\"]\r\n\r\n   def get_proxy(data):\r\n       return data[\"proxy\"]\r\n\r\n   @browser(profile=get_profile, proxy=get_proxy)\r\n   def scrape_heading_task(driver: Driver, data):\r\n       profile, proxy = driver.config.profile, driver.config.proxy\r\n       print(profile, proxy)\r\n       return profile, proxy\r\n\r\n   data = [\r\n       {\"profile\": \"pikachu\", \"proxy\": \"http://142.250.77.228:8000\"},\r\n       {\"profile\": \"greyninja\", \"proxy\": \"http://142.250.77.229:8000\"},\r\n   ]\r\n\r\n   scrape_heading_task(data)\r\n   ```\r\n\r\n2. Directly passing configuration values when calling the decorated function:\r\n   - Pass the profile and proxy values directly as arguments to the decorated function when calling it.\r\n\r\n   Example:\r\n   ```python\r\n   from botasaurus.browser import browser, Driver\r\n\r\n   @browser\r\n   def scrape_heading_task(driver: Driver, data):\r\n       profile, proxy = driver.config.profile, driver.config.proxy\r\n       print(profile, proxy)\r\n       return profile, proxy\r\n\r\n   scrape_heading_task(\r\n       profile='pikachu',  # Directly pass the profile\r\n       proxy=\"http://142.250.77.228:8000\",  # Directly pass the proxy\r\n   )\r\n   ```\r\n\r\nPS: Most Botasaurus decorators allow passing functions to derive configurations from data parameters. Check the decorator's argument type hint to see if it supports this functionality.\r\n\r\n\r\n### What is the best way to manage profile-specific data like name, age across multiple profiles?\r\n\r\nTo store data related to the active profile, use `driver.profile`. Here's an example:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\ndef get_profile(data):\r\n    return data[\"profile\"]\r\n\r\n@browser(profile=get_profile)\r\ndef run_profile_task(driver: Driver, data):\r\n    # Set profile data\r\n    driver.profile = {\r\n        'name': 'Amit Sharma',\r\n        'age': 30\r\n    }\r\n\r\n    # Update the name in the profile\r\n    driver.profile['name'] = 'Amit Verma'\r\n\r\n    # Delete the age from the profile\r\n    del driver.profile['age']\r\n\r\n    # Print the updated profile\r\n    print(driver.profile)  # Output: {'name': 'Amit Verma'}\r\n\r\n    # Delete the entire profile\r\n    driver.profile = None\r\n\r\nrun_profile_task([{\"profile\": \"amit\"}])\r\n```\r\n\r\nFor managing all profiles, use the `Profiles` utility. Here's an example:\r\n\r\n```python\r\nfrom botasaurus.profiles import Profiles\r\n\r\n# Set profiles\r\nProfiles.set_profile('amit', {'name': 'Amit Sharma', 'age': 30})\r\nProfiles.set_profile('rahul', {'name': 'Rahul Verma', 'age': 30})\r\n\r\n# Get a profile\r\nprofile = Profiles.get_profile('amit')\r\nprint(profile)  # Output: {'name': 'Amit Sharma', 'age': 30}\r\n\r\n# Get all profiles\r\nall_profiles = Profiles.get_profiles()\r\nprint(all_profiles)  # Output: [{'name': 'Amit Sharma', 'age': 30}, {'name': 'Rahul Verma', 'age': 30}]\r\n\r\n# Get all profiles in random order\r\nrandom_profiles = Profiles.get_profiles(random=True)\r\nprint(random_profiles)  # Output: [{'name': 'Rahul Verma', 'age': 30}, {'name': 'Amit Sharma', 'age': 30}] in random order\r\n\r\n# Delete a profile\r\nProfiles.delete_profile('amit')\r\n```\r\n\r\nNote: All profile data is stored in the `profiles.json` file in the current working directory.\r\n![profiles](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/profiles.png)\r\n\r\n### What are some common methods in Botasaurus Driver?\r\n\r\nBotasaurus Driver provides several handy methods for web automation tasks, such as:\r\n\r\n- Visiting URLs:\r\n  ```python\r\n  driver.get(\"https://www.example.com\")\r\n  driver.google_get(\"https://www.example.com\")  # Use Google as the referer [Recommended]\r\n  driver.get_via(\"https://www.example.com\", referer=\"https://duckduckgo.com/\")  # Use custom referer\r\n  driver.get_via_this_page(\"https://www.example.com\")  # Use current page as referer\r\n  ```\r\n\r\n- Finding elements:\r\n  ```python\r\n  from botasaurus.browser import Wait\r\n  search_results = driver.select(\".search-results\", wait=Wait.SHORT)  # Wait for up to 4 seconds for the element to be present, return None if not found\r\n  all_links = driver.select_all(\"a\")  # Get all elements matching the selector\r\n  search_results = driver.wait_for_element(\".search-results\", wait=Wait.LONG)  # Wait for up to 8 seconds for the element to be present, raise exception if not found\r\n  hello_mom = driver.get_element_with_exact_text(\"Hello Mom\", wait=Wait.VERY_LONG)  # Wait for up to 16 seconds for an element having the exact text \"Hello Mom\"\r\n  ```\r\n\r\n- Interacting with elements:\r\n  ```python\r\n  driver.type(\"input[name='username']\", \"john_doe\")  # Type into an input field\r\n  driver.click(\"button.submit\")  # Click an element\r\n  element = driver.select(\"button.submit\")\r\n  element.click()  # Click on an element\r\n  element.select_option(\"select#fruits\", index=2)  # Select an option\r\n  ```\r\n\r\n- Retrieving element properties:\r\n  ```python\r\n  header_text = driver.get_text(\"h1\")  # Get text content\r\n  error_message = driver.get_element_containing_text(\"Error: Invalid input\")\r\n  image_url = driver.select(\"img.logo\").get_attribute(\"src\")  # Get attribute value\r\n  ```\r\n\r\n- Working with parent-child elements:\r\n  ```python\r\n  parent_element = driver.select(\".parent\")\r\n  child_element = parent_element.select(\".child\")\r\n  child_element.click()  # Click child element\r\n  ```\r\n\r\n- Executing JavaScript:\r\n  ```python\r\n  result = driver.run_js(\"script.js\") # Run a JavaScript file located in the current working directory.\r\n  result = driver.run_js(\"return document.title\")\r\n  pikachu = driver.run_js(\"return args.pokemon\", {\"pokemon\": 'pikachu'}) # args can be a dictionary, list, string, etc.\r\n  text_content = driver.select(\"body\").run_js(\"(el) =\u003e el.textContent\")\r\n  ```\r\n\r\n- Enable human mode to perform, human-like mouse movements and say sayonara to detection:\r\n  ```python\r\n  # Navigate to Cloudflare's Turnstile Captcha demo\r\n  driver.get(\r\n    \"https://nopecha.com/demo/cloudflare\",\r\n  )\r\n\r\n  # Wait for page to fully load\r\n  driver.long_random_sleep()\r\n  \r\n  # Locate iframe containing the Cloudflare challenge\r\n  iframe = driver.get_element_at_point(160, 290)\r\n  \r\n  # Find checkbox element within the iframe\r\n  checkbox = iframe.get_element_at_point(30, 30)\r\n\r\n  # Enable human mode for realistic, human-like mouse movements\r\n  driver.enable_human_mode()\r\n\r\n  # Click the checkbox to solve the challenge\r\n  checkbox.click()\r\n\r\n  # (Optional) Disable human mode if no longer needed  \r\n  driver.disable_human_mode()\r\n\r\n  # Pause execution, for inspection\r\n  driver.prompt()\r\n  ```\r\n  \r\n  ![human-mode-demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/human-mode-demo.gif)\r\n\r\n- Drag and Drop:\r\n  ```python\r\n  # Open React DnD tutorial  \r\n  driver.get(\"https://react-dnd.github.io/react-dnd/examples/tutorial\")  \r\n\r\n  # Select draggable and droppable elements  \r\n  draggable = driver.select('[draggable=\"true\"]')  \r\n  droppable = driver.select('[data-testid=\"(3,6)\"]')  \r\n\r\n  # Perform drag-and-drop  \r\n  draggable.drag_and_drop_to(droppable)  \r\n\r\n  # Pause execution, for inspection\r\n  driver.prompt()  \r\n  ```\r\n\r\n  ![drag-and-drop-demo](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/drag-and-drop-demo.gif)\r\n\r\n- Selecting Shadow Root Elements:\r\n  ```python\r\n  # Visit the website\r\n  driver.get(\"https://nopecha.com/demo/cloudflare\")\r\n  \r\n  # Wait for page to fully load\r\n  driver.long_random_sleep()\r\n  \r\n  # Locate the element containing shadow root\r\n  shadow_root_element = driver.select('[name=\"cf-turnstile-response\"]').parent\r\n  \r\n  # Access the iframe\r\n  iframe = shadow_root_element.get_shadow_root()\r\n\r\n  # Access the nested shadow DOM inside the iframe \r\n  content = iframe.get_shadow_root()\r\n  \r\n  # print the text content of the \"label\" element.\r\n  print(content.select(\"label\", wait = 8).text)\r\n\r\n  # Pause execution, for inspection\r\n  driver.prompt()\r\n  ```\r\n![Selecting Shadow Root Elements](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/selecting-shadow-root-elements.gif)\r\n\r\n- Monitoring requests:\r\n  ```python\r\n  from botasaurus.browser import browser, Driver, cdp\r\n\r\n  @browser()\r\n  def scrape_responses_task(driver: Driver, data):\r\n      # Define a handler function that will be called after a response is received\r\n      def after_response_handler(\r\n          request_id: str,\r\n          response: cdp.network.Response,\r\n          event: cdp.network.ResponseReceived,\r\n      ):\r\n          # Extract URL, status, and headers from the response\r\n          url = response.url\r\n          status = response.status\r\n          headers = response.headers\r\n          \r\n          # Print the response details \r\n          print(\r\n              \"after_response_handler\",\r\n              {\r\n                  \"request_id\": request_id,\r\n                  \"url\": url,\r\n                  \"status\": status,\r\n                  \"headers\": headers,\r\n              },\r\n          )\r\n\r\n          # Append the request ID to the driver's responses list\r\n          driver.responses.append(request_id)\r\n\r\n      # Register the after_response_handler to be called after each response is received\r\n      driver.after_response_received(after_response_handler)\r\n\r\n      # Navigate to the specified URL\r\n      driver.get(\"https://example.com/\")\r\n\r\n      # Collect all the responses that were appended during the navigation\r\n      collected_responses = driver.responses.collect()\r\n      \r\n      # Save it in output/scrape_responses_task.json\r\n      return collected_responses\r\n\r\n  # Execute the scraping task\r\n  scrape_responses_task()\r\n  ```  \r\n\r\n- Working with iframes:\r\n  ```python\r\n  driver.get(\"https://www.freecodecamp.org/news/using-entity-framework-core-with-mongodb/\")\r\n  iframe = driver.get_iframe_by_link(\"www.youtube.com/embed\") \r\n  # OR the following works as well\r\n  # iframe = driver.select_iframe(\".embed-wrapper iframe\") \r\n  freecodecamp_youtube_subscribers_count = iframe.select(\".ytp-title-expanded-subtitle\").text\r\n  print(freecodecamp_youtube_subscribers_count)\r\n  ```\r\n- Executing CDP Command:\r\n  ```python\r\n  from botasaurus.browser import browser, Driver, cdp\r\n  driver.run_cdp_command(cdp.page.navigate(url='https://stackoverflow.blog/open-source'))\r\n  ```\r\n\r\n- Miscellaneous:\r\n  ```python\r\n  form.type(\"input[name='password']\", \"secret_password\")  # Type into a form field\r\n  container.is_element_present(\".button\")  # Check element presence\r\n  page_html = driver.page_html  # Current page HTML\r\n  driver.select(\".footer\").scroll_into_view()  # Scroll element into view\r\n  driver.close()  # Close the browser\r\n  ```\r\n\r\n### How Can I Pause the Browser to Inspect Website when Developing the Scraper?\r\n\r\nTo pause the scraper and wait for user input before proceeding, use `driver.prompt()`:\r\n\r\n```python\r\ndriver.prompt()\r\n```\r\n\r\n### How do I configure authenticated proxies with SSL in Botasaurus?\r\n\r\nProxy providers like BrightData, IPRoyal, and others typically provide authenticated proxies in the format \"http://username:password@proxy-provider-domain:port\". For example, \"http://greyninja:awesomepassword@geo.iproyal.com:12321\".\r\n\r\nHowever, if you use an authenticated proxy with a library like seleniumwire to visit a website using Cloudflare, or Datadome, you are GUARANTEED to be identified because you are using a non-SSL connection.\r\n\r\nTo verify this, run the following code:\r\n\r\nFirst, install the necessary packages:\r\n```bash \r\npython -m pip install selenium_wire\r\n```\r\n\r\nThen, execute this Python script:\r\n```python\r\nfrom seleniumwire import webdriver  # Import from seleniumwire\r\n\r\n# Define the proxy\r\nproxy_options = {\r\n    'proxy': {\r\n        'http': 'http://username:password@proxy-provider-domain:port', # TODO: Replace with your own proxy\r\n        'https': 'http://username:password@proxy-provider-domain:port', # TODO: Replace with your own proxy\r\n    }\r\n}\r\n\r\n# Install and set up the driver\r\ndriver = webdriver.Chrome(seleniumwire_options=proxy_options)\r\n\r\n# Visit the desired URL\r\nlink = 'https://fingerprint.com/products/bot-detection/'\r\ndriver.get(\"https://www.google.com/\")\r\ndriver.execute_script(f'window.location.href = \"{link}\"')\r\n\r\n# Prompt for user input\r\ninput(\"Press Enter to exit...\")\r\n\r\n# Clean up\r\ndriver.quit()\r\n```\r\n\r\nYou will SURELY be identified:\r\n\r\n![identified](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/seleniumwireblocked.png)\r\n\r\nHowever, using proxies with Botasaurus solves this issue. See the difference by running the following code:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(proxy=\"http://username:password@proxy-provider-domain:port\") # TODO: Replace with your own proxy \r\ndef scrape_heading_task(driver: Driver, data):\r\n    driver.google_get(\"https://fingerprint.com/products/bot-detection/\")\r\n    driver.prompt()\r\n\r\nscrape_heading_task()    \r\n```  \r\n\r\nResult: \r\n![not identified](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/botasurussuccesspage.png)\r\n\r\nImportant Note: To run the code above, you will need [Node.js](https://nodejs.org/en) installed.\r\n\r\n### Why am I getting a socket connection error when using a proxy to access a website?\r\n\r\nCertain proxy providers like BrightData will block access to specific websites. To determine if this is the case, run the following code:\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(proxy=\"http://username:password@proxy-provider-domain:port\")  # TODO: Replace with your own proxy\r\ndef visit_what_is_my_ip(driver: Driver, data):\r\n    driver.get(\"https://whatismyipaddress.com/\")\r\n    driver.prompt()\r\n\r\nvisit_what_is_my_ip()\r\n```\r\n\r\nIf you can successfully access [whatismyipaddress.com](https://whatismyipaddress.com/) but not the website you're attempting to scrape, it means the proxy provider is blocking access to that particular website. \r\n\r\nIn such situations, the only solution is to switch to a different proxy provider.\r\n\r\nSome good proxy providers we personally use are:\r\n\r\n- For Rotating Datacenter Proxies: **BrightData Datacenter Proxies**, which cost around $0.6 per GB on a pay-as-you-go basis. No KYC is required.\r\n- For Rotating Residential Proxies: **IPRoyal Royal Residential Proxies**, which cost around $7 per GB on a pay-as-you-go basis. No KYC is required.\r\n\r\nAs always, nothing good in life comes free. Proxies are expensive, and will take up almost all of your scraping costs. \r\n\r\nSo, use proxies only when you need them, and prefer request-based scrapers over browser-based scrapers to save bandwidth.\r\n\r\nNote: BrightData and IPRoyal have not paid us. We are recommending them based on our personal experience.\r\n\r\n### Which country should I choose when using proxies for web scraping?\r\n\r\nThe United States is often the best choice because:\r\n- The United States has a highly developed internet infrastructure and is home to numerous data centers, ensuring faster internet speeds.\r\n- Most global companies host their websites in the US, so using a US proxy will result in faster scraping speeds.\r\n\r\n### Should I use a proxy for web scraping?\r\n\r\nONLY IF you encounter IP blocks.\r\n\r\nSadly, most scrapers unnecessarily use proxies, even when they are not needed. Everything seems like a nail when you have a hammer.\r\n\r\nWe have seen scrapers which can easily access hundreds of thousands of protected pages using the @browser module on home Wi-Fi without any issues.\r\n\r\nSo, as a best practice scrape using the @browser module on your home Wi-Fi first. Only resort to proxies when you encounter IP blocks. \r\n\r\nThis practice will save you a considerable amount of time (as proxies are really slow) and money (as proxies are expensive as well).\r\n\r\n### How to configure the Request Decorator?\r\n\r\nThe Request Decorator is used to make humane requests. Under the hood, it uses botasaurus-requests, a library based on hrequests, which incorporates important features like:\r\n- Using browser-like headers in the correct order.\r\n- Makes a browser-like connection with correct ciphers.\r\n- Uses `google.com` referer by default to make it appear as if the user has arrived from google search.\r\n\r\nAlso, The Request Decorator allows you to configure proxy as follows:\r\n\r\n```python\r\n@request(\r\n    proxy=\"http://username:password@proxy-provider-domain:port\"\r\n)    \r\n```\r\n\r\n\r\n### What Options Can I Configure in all 3 Decorators?\r\n\r\nAll 3 decorators allow you to configure the following options:\r\n\r\n- Parallel Execution:\r\n- Caching Results\r\n- Passing Common Metadata\r\n- Asynchronous Queues\r\n- Asynchronous Execution\r\n- Handling Crashes\r\n- Configuring Output\r\n- Exception Handling\r\n\r\nLet's dive into each of these options and in later sections we will see their real-world applications.\r\n\r\n#### `parallel`\r\n\r\nThe `parallel` option allows you to scrape data in parallel by launching multiple browser/request/task instances simultaneously. This can significantly speed up the scraping process.\r\n\r\nRun the example below to see parallelization in action:\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(parallel=3, data=[\"https://stackoverflow.blog/open-source\", \"https://stackoverflow.blog/ai\", \"https://stackoverflow.blog/productivity\",])\r\ndef scrape_heading_task(driver: Driver, link):\r\n    driver.get(link)\r\n    heading = driver.get_text('h1')\r\n    return heading\r\n\r\nscrape_heading_task()    \r\n```\r\n\r\n#### `cache`\r\n\r\nThe `cache` option enables caching of web scraping results to avoid re-scraping the same data. This can significantly improve performance and reduce redundant requests.\r\n\r\n\r\nRun the example below to see how caching works:\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(cache=True, data=[\"https://stackoverflow.blog/open-source\", \"https://stackoverflow.blog/ai\", \"https://stackoverflow.blog/productivity\",])\r\ndef scrape_heading_task(driver: Driver, link):\r\n    driver.get(link)\r\n    heading = driver.get_text('h1')\r\n    return heading\r\n\r\nprint(scrape_heading_task())\r\nprint(scrape_heading_task())  # Data will be fetched from cache immediately \r\n```\r\n\r\nNote: Caching is one of the most important features of Botasaurus.\r\n\r\n#### `metadata`\r\n\r\nThe metadata option allows you to pass common information shared across all data items. This can include things like API keys, browser cookies, or any other data that remains constant throughout the scraping process.\r\n\r\nIt is commonly used with caching to exclude details like API keys and browser cookies from the cache key.\r\n\r\nHere's an example of how to use the `metadata` option:\r\n```python\r\nfrom botasaurus.task import task\r\n\r\n@task()\r\ndef scrape_heading_task(driver: Driver, data, metadata):\r\n    print(\"metadata:\", metadata)\r\n    print(\"data:\", data)\r\n\r\ndata = [\r\n    {\"profile\": \"pikachu\", \"proxy\": \"http://142.250.77.228:8000\"},\r\n    {\"profile\": \"greyninja\", \"proxy\": \"http://142.250.77.229:8000\"},\r\n]\r\nscrape_heading_task(\r\n  data, \r\n  metadata={\"api_key\": \"BDEC26...\"}\r\n)\r\n```\r\n\r\n#### `async_queue`\r\n\r\nIn the world of web scraping, there are only two types of scrapers:\r\n\r\n1. Dataset Scrapers: These extract data from websites and store it as datasets. Companies like Bright Data use them to build datasets for Crunchbase, Indeed, etc.\r\n\r\n2. Real-time Scrapers: These fetch data from sources in real-time, like SERP APIs that provide Google and DuckDuckGo search results.\r\n\r\nWhen building real-time scrapers, speed is paramount because customers are waiting for requests to complete. The `async_queue` feature is incredibly useful in such cases.\r\n\r\n`async_queue` allows you to run scraping tasks asynchronously in a queue and gather the results using the `.get()` method. \r\n\r\nA great use case for `async_queue` is scraping Google Maps. Instead of scrolling through the list of places and then scraping the details of each place sequentially, you can use `async_queue` to:\r\n1. Scroll through the list of places.\r\n2. Simultaneously make HTTP requests to scrape the details of each place in the background.\r\n    \r\nBy executing the scrolling and requesting tasks concurrently, you can significantly speed up the scraper.\r\n\r\nRun the code below to see browser scrolling and request scraping happening concurrently (really cool, must try!):\r\n```python\r\nfrom botasaurus.browser import browser, Driver, AsyncQueueResult\r\nfrom botasaurus.request import request, Request\r\nimport json\r\n\r\ndef extract_title(html):\r\n    return json.loads(\r\n        html.split(\";window.APP_INITIALIZATION_STATE=\")[1].split(\";window.APP_FLAGS\")[0]\r\n    )[5][3][2][1]\r\n\r\n@request(\r\n    parallel=5,\r\n    async_queue=True,\r\n    max_retry=5,\r\n)\r\ndef scrape_place_title(request: Request, link, metadata):\r\n    cookies = metadata[\"cookies\"]\r\n    html = request.get(link, cookies=cookies, timeout=12).text\r\n    title = extract_title(html)\r\n    print(\"Title:\", title)\r\n    return title\r\n\r\ndef has_reached_end(driver):\r\n    return driver.select('p.fontBodyMedium \u003e span \u003e span') is not None\r\n\r\ndef extract_links(driver):\r\n    return driver.get_all_links('[role=\"feed\"] \u003e div \u003e div \u003e a')\r\n\r\n@browser()\r\ndef scrape_google_maps(driver: Driver, link):\r\n    driver.google_get(link, accept_google_cookies=True)  # accepts google cookies popup\r\n\r\n    scrape_place_obj: AsyncQueueResult = scrape_place_title()  # initialize the async queue for scraping places\r\n    cookies = driver.get_cookies_dict()  # get the cookies from the driver\r\n\r\n    while True:\r\n        links = extract_links(driver)  # get the links to places\r\n        scrape_place_obj.put(links, metadata={\"cookies\": cookies})  # add the links to the async queue for scraping\r\n\r\n        print(\"scrolling\")\r\n        driver.scroll_to_bottom('[role=\"feed\"]')  # scroll to the bottom of the feed\r\n\r\n        if has_reached_end(driver):  # we have reached the end, let's break buddy\r\n            break\r\n\r\n    results = scrape_place_obj.get()  # get the scraped results from the async queue\r\n    return results\r\n\r\nscrape_google_maps(\"https://www.google.com/maps/search/web+developers+in+bangalore\")\r\n```\r\n\r\n#### `run_async`\r\n\r\nSimilarly, the `run_async` option allows you to execute scraping tasks asynchronously, enabling concurrent execution.\r\n\r\nSimilar to `async_queue`, you can use the `.get()` method to retrieve the results of an asynchronous task.\r\n\r\nCode Example:\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\nfrom time import sleep\r\n\r\n@browser(run_async=True)\r\ndef scrape_heading(driver: Driver, data):\r\n    sleep(5)\r\n    return {}\r\n\r\nif __name__ == \"__main__\":\r\n    result1 = scrape_heading()  # Launches asynchronously\r\n    result2 = scrape_heading()  # Launches asynchronously\r\n\r\n    result1.get()  # Wait for the first result\r\n    result2.get()  # Wait for the second result\r\n```\r\n\r\n#### `close_on_crash`\r\n\r\nThe `close_on_crash` option determines the behavior of the scraper when an exception occurs:\r\n- If set to `False` (default):\r\n  - The scraper will make a beep sound and pause the browser.\r\n  - This makes debugging easier by keeping the browser open at the point of the crash.\r\n  - Use this setting during development and testing.\r\n- If set to `True`:\r\n  - The scraper will close the browser and continue with the rest of the data items.\r\n  - This is suitable for production environments when you are confident that your scraper is robust.\r\n  - Use this setting to avoid interruptions and ensure the scraper processes all data items.\r\n\r\n```python\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@browser(\r\n    close_on_crash=False  # Determines whether the browser is paused (default: False) or closed when an error occurs\r\n)\r\ndef scrape_heading_task(driver: Driver, data):\r\n    raise Exception(\"An error occurred during scraping.\")\r\n\r\nscrape_heading_task()  \r\n```\r\n\r\n#### `output` and `output_formats`\r\n\r\nBy default, Botasaurus saves the result of scraping in the `output/{your_scraping_function_name}.json` file. Let's learn about various ways to configure the output.\r\n\r\n1. **Change Output Filename**: Use the `output` parameter in the decorator to specify a custom filename for the output.\r\n```python\r\nfrom botasaurus.task import task\r\n\r\n@task(output=\"my-output\")\r\ndef scrape_heading_task(data): \r\n    return {\"heading\": \"Hello, Mom!\"}\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n2. **Disable Output**: If you don't want any output to be saved, set `output` to `None`.\r\n```python\r\nfrom botasaurus.task import task\r\n\r\n@task(output=None)\r\ndef scrape_heading_task(data): \r\n    return {\"heading\": \"Hello, Mom!\"}\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n3. **Dynamically Write Output**: To dynamically write output based on data and result, pass a function to the `output` parameter:\r\n```python\r\nfrom botasaurus.task import task\r\nfrom botasaurus import bt\r\n\r\ndef write_output(data, result):\r\n    json_filename = bt.write_json(result, 'data')\r\n    excel_filename = bt.write_excel(result, 'data')\r\n    bt.zip_files([json_filename, excel_filename]) # Zip the JSON and Excel files for easy delivery to the customer\r\n\r\n@task(output=write_output)  \r\ndef scrape_heading_task(data): \r\n    return {\"heading\": \"Hello, Mom!\"}\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n4. **Upload File to S3**: Use `bt.upload_to_s3` to upload file to S3 bucket.\r\n```python\r\nfrom botasaurus.task import task\r\nfrom botasaurus import bt\r\n\r\ndef write_output(data, result):\r\n    json_filename = bt.write_json(result, 'data')\r\n    bt.upload_to_s3(json_filename, 'my-magical-bucket', \"AWS_ACCESS_KEY\", \"AWS_SECRET_KEY\")\r\n\r\n@task(output=write_output)  \r\ndef scrape_heading_task(data): \r\n    return {\"heading\": \"Hello, Mom!\"}\r\n\r\nscrape_heading_task()\r\n```\r\n\r\n5.**Save Outputs in Multiple Formats**: Use the `output_formats` parameter to save outputs in different formats like JSON and EXCEL.\r\n```python\r\nfrom botasaurus.task import task\r\n\r\n@task(output_formats=[bt.Formats.JSON, bt.Formats.EXCEL])  \r\ndef scrape_heading_task(data): \r\n    return {\"heading\": \"Hello, Mom!\"}\r\n\r\nscrape_heading_task()\r\n```\r\n\r\nPRO TIP: When delivering data to customers, provide the dataset in JSON and Excel formats. Avoid CSV unless the customer asks, because Microsoft Excel has a hard time rendering CSV files with nested JSON.\r\n\r\n**CSV vs Excel**\r\n![csv-vs-excel](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/csv-vs-excel.png)\r\n\r\n#### Exception Handling Options\r\n\r\nBotasaurus provides various exception handling options to make your scrapers more robust:\r\n- `max_retry`: By default, any failed task is not retried. You can specify the maximum number of times to retry scraping when an error occurs using the `max_retry` option.\r\n- `retry_wait`: Specifies the waiting time between retries.\r\n- `raise_exception`: By default, Botasaurus does not raise an exception when an error occurs during scraping, because let's say you are keeping your PC running overnight to scrape 10,000 links. If one link fails, you really don't want to stop the entire scraping process, and ruin your morning by seeing an unfinished dataset.\r\n- `must_raise_exceptions`: Specifies exceptions that must be raised, even if `raise_exception` is set to `False`.\r\n- `create_error_logs`: Determines whether error logs should be created when exceptions occur. In production, when scraping hundreds of thousands of links, it's recommended to set `create_error_logs` to `False` to avoid using computational resources for creating error logs.\r\n\r\n```python\r\n@browser(\r\n    raise_exception=True,  # Raise an exception and halt the scraping process when an error occurs\r\n    max_retry=5,  # Retry scraping a failed task a maximum of 5 times\r\n    retry_wait=10,  # Wait for 10 seconds before retrying a failed task\r\n    must_raise_exceptions=[CustomException],  # Definitely raise CustomException, even if raise_exception is set to False\r\n    create_error_logs=False  # Disable the creation of error logs to optimize scraper performance\r\n)\r\ndef scrape_heading_task(driver: Driver, data):\r\n  # ...\r\n```\r\n\r\n### What are some examples of common web scraping utilities provided by Botasaurus that make scraping easier?\r\n\r\n#### bt Utility\r\n\r\nThe `bt` utility provides helper functions for:\r\n\r\n- Writing and reading JSON, EXCEL, and CSV files\r\n- Data cleaning\r\n\r\nSome key functions are:\r\n\r\n- `bt.write_json` and `bt.read_json`: Easily write and read JSON files.\r\n```python\r\nfrom botasaurus import bt\r\n\r\ndata = {\"name\": \"pikachu\", \"power\": 101}\r\nbt.write_json(data, \"output\")\r\nloaded_data = bt.read_json(\"output\")\r\n```\r\n\r\n- `bt.write_excel` and `bt.read_excel`: Easily write and read EXCEL files.\r\n```python\r\nfrom botasaurus import bt\r\n\r\ndata = {\"name\": \"pikachu\", \"power\": 101}\r\nbt.write_excel(data, \"output\")\r\nloaded_data = bt.read_excel(\"output\")\r\n```\r\n\r\n- `bt.write_csv` and `bt.read_csv`: Easily write and read CSV files.\r\n```python\r\nfrom botasaurus import bt\r\n\r\ndata = {\"name\": \"pikachu\", \"power\": 101}\r\nbt.write_csv(data, \"output\")\r\nloaded_data = bt.read_csv(\"output\")\r\n```\r\n\r\n- `bt.write_html` and `bt.read_html`: Write HTML content to a file.\r\n```python\r\nfrom botasaurus import bt\r\n\r\nhtml_content = \"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eHello, Mom!\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\"\r\nbt.write_html(html_content, \"output\")\r\n```\r\n\r\n- `bt.write_temp_json`, `bt.write_temp_csv`, `bt.write_temp_html`: Write temporary JSON, CSV, or HTML files for debugging purposes.\r\n```python\r\nfrom botasaurus import bt\r\n\r\ndata = {\"name\": \"pikachu\", \"power\": 101}\r\nbt.write_temp_json(data)\r\nbt.write_temp_csv(data)\r\nbt.write_temp_html(\"\u003chtml\u003e\u003cbody\u003e\u003ch1\u003eHello, Mom!\u003c/h1\u003e\u003c/body\u003e\u003c/html\u003e\")\r\n```\r\n\r\n- Data cleaning functions like `bt.extract_numbers`, `bt.extract_links`, `bt.remove_html_tags`, and more.\r\n```python\r\ntext = \"The price is $19.99 and the website is https://www.example.com\"\r\nnumbers = bt.extract_numbers(text)  # [19.99]\r\nlinks = bt.extract_links(text)  # [\"https://www.example.com\"]\r\n```\r\n\r\n#### Local Storage Utility\r\n\r\nThe Local Storage utility allows you to store and retrieve key-value pairs, which can be useful for maintaining state between scraper runs. \r\n\r\nHere's how to use it:\r\n\r\n```python\r\nfrom botasaurus.local_storage import LocalStorage\r\n\r\nLocalStorage.set_item(\"credits_used\", 100)\r\nprint(LocalStorage.get_item(\"credits_used\", 0))\r\n```\r\n\r\n#### soupify Utility\r\n\r\nThe `soupify` utility creates a BeautifulSoup object from a Driver, Requests response, Driver Element, or HTML string.\r\n\r\n```python\r\nfrom botasaurus.soupify import soupify\r\nfrom botasaurus.request import request, Request\r\nfrom botasaurus.browser import browser, Driver\r\n\r\n@request\r\ndef get_heading_from_request(req: Request, data):\r\n   \"\"\"\r\n   Get the heading of a web page using the request object.\r\n   \"\"\"\r\n   response = req.get(\"https://www.example.com\")\r\n   soup = soupify(response)\r\n   heading = soup.find(\"h1\").text\r\n   print(f\"Page Heading: {heading}\")\r\n\r\n@browser\r\ndef get_heading_from_driver(driver: Driver, data):\r\n   \"\"\"\r\n   Get the heading of a web page using the driver object.\r\n   \"\"\"\r\n   driver.get(\"https://www.example.com\")\r\n\r\n   # Get the heading from the entire page\r\n   page_soup = soupify(driver)\r\n   page_heading = page_soup.find(\"h1\").text\r\n   print(f\"Heading from Driver's Soup: {page_heading}\")\r\n\r\n   # Get the heading from the body element\r\n   body_soup = soupify(driver.select(\"body\"))\r\n   body_heading = body_soup.find(\"h1\").text\r\n   print(f\"Heading from Element's Soup: {body_heading}\")\r\n\r\n# Call the functions\r\nget_heading_from_request()\r\nget_heading_from_driver()\r\n```\r\n\r\n#### IP Utils\r\n\r\nIP Utils provide functions to get information about the current IP address, such as the IP itself, country, ISP, and more:\r\n\r\n```python\r\nfrom botasaurus.ip_utils import IPUtils\r\n\r\n# Get the current IP address\r\ncurrent_ip = IPUtils.get_ip()\r\nprint(current_ip)\r\n# Output: 47.31.226.180\r\n\r\n# Get detailed information about the current IP address\r\nip_info = IPUtils.get_ip_info()\r\nprint(ip_info)\r\n# Output: {\r\n#     \"ip\": \"47.31.226.180\",\r\n#     \"country\": \"IN\",\r\n#     \"region\": \"Delhi\",\r\n#     \"city\": \"Delhi\",\r\n#     \"postal\": \"110001\",\r\n#     \"coordinates\": \"28.6519,77.2315\",\r\n#     \"latitude\": \"28.6519\",\r\n#     \"longitude\": \"77.2315\",\r\n#     \"timezone\": \"Asia/Kolkata\",\r\n#     \"org\": \"AS55836 Reliance Jio Infocomm Limited\"\r\n# }\r\n```\r\n\r\n#### Cache Utility\r\nThe Cache utility in Botasaurus allows you to manage cached data for your scraper. You can put, get, has, remove, and clear cache data.\r\n\r\n*Basic Usage*\r\n\r\n```python\r\nfrom botasaurus.task import task\r\nfrom botasaurus.cache import Cache\r\n\r\n# Example scraping function\r\n@task\r\ndef scrape_data(data):\r\n    # Your scraping logic here\r\n    return {\"processed\": data}\r\n\r\n# Sample data for scraping\r\ninput_data = {\"key\": \"value\"}\r\n\r\n# Adding data to the cache\r\nCache.put('scrape_data', input_data, scrape_data(input_data))\r\n\r\n# Checking if data is in the cache\r\nif Cache.has('scrape_data', input_data):\r\n    # Retrieving data from the cache\r\n    cached_data = Cache.get('scrape_data', input_data)\r\n    print(f\"Cached data: {cached_data}\")\r\n\r\n# Removing specific data from the cache\r\nCache.remove('scrape_data', input_data)\r\n\r\n# Clearing the complete cache for the scrape_data function\r\nCache.clear('scrape_data')\r\n```\r\n\r\n**Advanced Usage for large-scale scraping projects**\r\n\r\n*Count Cached Items*\r\n\r\nYou can count the number of items cached for a particular function, which can serve as a scraping progress bar.\r\n\r\n```python\r\nfrom botasaurus.cache import Cache\r\n\r\nCache.print_cached_items_count('scraping_function')\r\n```\r\n\r\n*Filter Cached/Uncached Items*\r\n\r\nYou can filter items that have been cached or not cached for a particular function.\r\n\r\n```python\r\nfrom botasaurus.cache import Cache\r\n\r\nall_items = ['1', '2', '3', '4', '5']\r\n\r\n# Get items that are cached\r\ncached_items = Cache.filter_items_in_cache('scraping_function', all_items)\r\nprint(cached_items)\r\n\r\n# Get items that are not cached\r\nuncached_items = Cache.filter_items_not_in_cache('scraping_function', all_items)\r\nprint(uncached_items)\r\n```\r\n\r\n*Delete Cache*\r\nThe cache for a function is stored in the `cache/{your_scraping_function_name}/` folder. To delete the cache, simply delete that folder.\r\n\r\n![delete-cache](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/delete-cache.png)\r\n\r\n\r\n*Delete Specific Items*\r\n\r\nYou can delete specific items from the cache for a particular function.\r\n\r\n```python\r\nfrom botasaurus.cache import Cache\r\n\r\nall_items = ['1', '2', '3', '4', '5']\r\ndeleted_count = Cache.delete_items('scraping_function', all_items)\r\nprint(f\"Deleted {deleted_count} items from the cache.\")\r\n```\r\n\r\n*Delete Items by Filter*  \r\n\r\nIn some cases, you may want to delete specific items from the cache based on a condition. For example, if you encounter honeypots (mock HTML served to dupe web scrapers) while scraping a website, you may want to delete those items from the cache.\r\n\r\n```python\r\ndef should_delete_item(item, result):\r\n    if 'Honeypot Item' in result:\r\n        return True  # Delete the item\r\n    return False  # Don't delete the item\r\n\r\nall_items = ['1', '2', '3', '4', '5']\r\n# List of items to iterate over, it is fine if the list contains items which have not been cached, as they will be simply ignored.\r\nCache.delete_items_by_filter('scraping_function', should_delete_item, all_items)\r\n```\r\n\r\nImportantly, be cautious and first use `delete_items_by_filter` on a small set of items which you want to be deleted. Here's an example:\r\n\r\n```python\r\nfrom botasaurus import bt\r\nfrom botasaurus.cache import Cache\r\n\r\ndef should_delete_item(item, result):\r\n    # TODO: Update the logic\r\n    if 'Honeypot Item' in result:\r\n        return True # Delete the item\r\n    return False # Don't delete the item\r\n\r\ntest_items = ['1', '2'] # TODO: update with target items\r\nscraping_function_name = 'scraping_function' # TODO:  update with target scraping function name\r\nCache.delete_items_by_filter(scraping_function_name, test_items, should_delete_item)\r\n\r\nfor item in test_items:\r\n    if Cache.has(scraping_function_name, item):\r\n        bt.prompt(f\"Item {item} was not deleted. Please review the logic of the should_delete_item function.\")\r\n```\r\n\r\n### How to Extract Links from a Sitemap?\r\n\r\nIn web scraping, it is a common use case to scrape product pages, blogs, etc. But before scraping these pages, you need to get the links to these pages.\r\n\r\nSadly, many developers unnecessarily increase their work by writing code to visit each page one by one and scrape links, which they could have easily obtained by just looking at the Sitemap.\r\n\r\nThe Botasaurus Sitemap Module makes this process easy as cake by allowing you to get all links or sitemaps using:\r\n- The homepage URL (e.g., `https://www.omkar.cloud/`)\r\n- A direct sitemap link (e.g., `https://www.omkar.cloud/sitemap.xml`)\r\n- A `.gz` compressed sitemap\r\n\r\nFor example, if you're an Angel Investor seeking innovative tech startups to invest in, G2 is an ideal platform to find such startups. You can run the following code to fetch over 190K+ product links from G2:\r\n\r\n```python\r\nfrom botasaurus import bt\r\nfrom botasaurus.sitemap import Sitemap, Filters, Extractors\r\n\r\nlinks = (\r\n    Sitemap(\"https://www.g2.com/sitemaps/sitemap_index.xml.gz\")\r\n    .filter(Filters.first_segment_equals(\"products\"))\r\n    .extract(Extractors.extract_link_upto_second_segment())\r\n    .write_links('g2-products')\r\n)\r\n```\r\n\r\n**Output:** \r\n\r\n![g2-sitemap-links.png](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/g2-sitemap-links.png)\r\n\r\nOr, let's say you're in the mood for some reading and looking for good stories. The following code will get you over 1000+ stories from [moralstories26.com](https://moralstories26.com/):\r\n\r\n```python\r\nfrom botasaurus import bt\r\nfrom botasaurus.sitemap import Sitemap, Filters\r\n\r\nlinks = (\r\n    Sitemap(\"https://moralstories26.com/\")\r\n    .filter(\r\n        Filters.has_exactly_1_segment(),\r\n        Filters.first_segment_not_equals(\r\n            [\"about\", \"privacy-policy\", \"akbar-birbal\", \"animal\", \"education\", \"fables\", \"facts\", \"family\", \"famous-personalities\", \"folktales\", \"friendship\", \"funny\", \"heartbreaking\", \"inspirational\", \"life\", \"love\", \"management\", \"motivational\", \"mythology\", \"nature\", \"quotes\", \"spiritual\", \"uncategorized\", \"zen\"]\r\n        ),\r\n    )\r\n    .write_links('moral-stories')\r\n)\r\n```\r\n\r\n**Output:** \r\n\r\n![moralstories26-sitemap-links.png](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/moralstories26-sitemap-links.png)\r\n\r\nAlso, before scraping a site, it's useful to identify the available sitemaps. This can be easily done with the following code:\r\n\r\n```python\r\nfrom botasaurus import bt\r\nfrom botasaurus.sitemap import Sitemap\r\n\r\nsitemaps = Sitemap(\"https://www.omkar.cloud/\").write_sitemaps('omkar-sitemaps')\r\n```\r\n\r\n**Output:** \r\n\r\n![omkar-sitemap-links.png](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/omkar-sitemap-links.png)\r\n\r\nTo ensure your scrapers run super fast, we cache the Sitemap, but you may want to periodically refresh the cache. To do so, pass the Cache.REFRESH parameter. \r\n\r\n```python\r\nfrom botasaurus import bt\r\nfrom botasaurus.sitemap import Sitemap, Filters, Extractors\r\nfrom botasaurus.cache import Cache\r\n\r\nlinks = (\r\n    Sitemap(\"https://moralstories26.com/\", cache=Cache.REFRESH)\r\n    .write_links('moral-stories')\r\n)\r\n```\r\n### How can I filter a list of links, similar to working with Sitemaps?\r\n\r\nFiltering links from a webpage is a common requirement in web scraping. For example, you might want to filter out all non-product pages.\r\n\r\n\r\nBotasaurus's `Links` module simplifies link filtering and extraction:\r\n\r\n```python\r\nfrom botasaurus.links import Links, Filters, Extractors\r\n\r\n# Sample list of links\r\nlinks = [\r\n    \"https://finance.yahoo.com/topic/stock-market-news/\",\r\n    \"https://finance.yahoo.com/topic/morning-brief/\", \r\n    \"https://finance.yahoo.com/quote/AAPL/\", \r\n    \"https://finance.yahoo.com/quote/GOOG/\"\r\n]\r\n\r\n# Filter and extract links\r\nfiltered_links = (\r\n    Links(links)\r\n    .filter(Filters.first_segment_equals(\"quote\"))\r\n    .extract(Extractors.extract_link_upto_second_segment())\r\n    .write('stocks')\r\n)\r\n```\r\n\r\n### What is the best way to use caching in Botasaurus?\r\n\r\nSadly, when using caching, most developers write a scraping function that scrapes the HTML and extracts the data from the HTML in the same function, like this:\r\n\r\n```python\r\nfrom botasaurus.request import request, Request\r\nfrom botasaurus.soupify import soupify\r\n\r\n@request\r\ndef scrape_data(request: Request, data):\r\n    # Visit the Link\r\n    response = request.get(data)\r\n    \r\n    # Create a BeautifulSoup object\r\n    soup = soupify(response)\r\n    \r\n    # Retrieve the heading element's text\r\n    heading = soup.find('h1').get_text()\r\n    \r\n    # Save the data as a JSON file in output/scrape_data.json\r\n    return {\"heading\": heading}\r\n\r\ndata_items = [\r\n    \"https://stackoverflow.blog/open-source\",\r\n    \"https://stackoverflow.blog/ai\",\r\n    \"https://stackoverflow.blog/productivity\",\r\n]\r\n\r\nscrape_data(data_items)\r\n```\r\n\r\nNow, let's say, after 50% of the dataset has been scraped, what if:\r\n- Your customer wants to add another data point (which is very likely), or\r\n- One of your BeautifulSoup selectors happens to be flaky and needs to be updated (which is super likely)?\r\n\r\nIn such cases, you will have to scrape all the pages again, which is painful as it will take a lot of time and incur high proxy costs.\r\n\r\nTo resolve this issue, you can:\r\n1. Write a function that only scrapes and caches the HTML.\r\n2. Write a separate function that calls the HTML scraping function, extracts data using BeautifulSoup, and caches the result.\r\n\r\nHere's a practical example:\r\n\r\n```python\r\nfrom bs4 import BeautifulSoup\r\nfrom botasaurus.task import task\r\nfrom botasaurus.request import request, Request\r\nfrom botasaurus.soupify import soupify\r\n\r\n@request(cache=True)\r\ndef scrape_html(request: Request, link):\r\n    # Scrape the HTML and cache it\r\n    html = request.get(link).text\r\n    return html\r\n\r\ndef extract_data(soup: BeautifulSoup):\r\n    # Extract the heading from the HTML\r\n    heading = soup.find(\"h1\").get_text()\r\n    return {\"heading\": heading}\r\n\r\n# Cache the scrape_data task as well\r\n@task(cache=True)\r\ndef scrape_data(link):\r\n    # Call the scrape_html function to get the cached HTML\r\n    html = scrape_html(link)\r\n    # Extract data from the HTML using the extract_data function\r\n    return extract_data(soupify(html))\r\n\r\ndata_items = [\r\n    \"https://stackoverflow.blog/open-source\",\r\n    \"https://stackoverflow.blog/ai\",\r\n    \"https://stackoverflow.blog/productivity\",\r\n]\r\n\r\nscrape_data(data_items)\r\n```\r\n\r\nWith this approach:\r\n- If you need to add data points or fix BeautifulSoup bugs, delete the `cache/scrape_data` folder and re-run the scraper.\r\n![delete-cache](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/delete-cache.png)\r\n- You only need to re-run the BeautifulSoup extraction, not the entire HTML scraping, saving time and proxy costs. Yahoo!\r\n\r\n**PRO TIP**: This approach also makes your `extract_data` code easier and faster to test, like this:\r\n\r\n```python\r\nfrom bs4 import BeautifulSoup\r\nfrom botasaurus import bt\r\n\r\ndef extract_data(soup: BeautifulSoup):\r\n    heading = soup.find('h1').get_text()\r\n    return {\"heading\": heading}\r\n\r\nif __name__ == '__main__':\r\n    # Will use the cached HTML and run the extract_data function again.\r\n    bt.write_temp_json(scrape_data(\"https://stackoverflow.blog/open-source\", cache=False))\r\n```\r\n### What are the recommended settings for each decorator to build a production-ready scraper in Botasaurus?\r\n\r\nFor websites with minimal protection, use the `Request` module.\r\n\r\nHere's a template for creating production-ready datasets using the `Request` module:\r\n\r\n```python\r\nfrom bs4 import BeautifulSoup\r\nfrom botasaurus.task import task\r\nfrom botasaurus.request import request, Request,NotFoundException\r\nfrom botasaurus.soupify import soupify\r\n\r\n@request(\r\n    # proxy='http://username:password@datacenter-proxy-domain:proxy-port', # Uncomment to use Proxy ONLY if you face IP blocking\r\n    cache=True,\r\n\r\n    max_retry=20, # Retry up to 20 times, which is a good default\r\n\r\n    output=None,\r\n\r\n    close_on_crash=True,\r\n    raise_exception=True,\r\n    create_error_logs=False,\r\n)\r\ndef scrape_html(request: Request, link):\r\n    # Scrape the HTML and cache it\r\n    response = request.get(link)\r\n    if response.status_code == 404:\r\n        # A Special Exception to skip retrying this link\r\n        raise NotFoundException(link)\r\n    response.raise_for_status()\r\n    return response.text\r\n\r\ndef extract_data(soup: BeautifulSoup):\r\n    # Extract the heading from the HTML\r\n    heading = soup.find(\"h1\").get_text()\r\n    return {\"heading\": heading}\r\n\r\n# Cache the scrape_data task as well\r\n@task(\r\n    cache=True,\r\n    close_on_crash=True,\r\n    create_error_logs=False,\r\n    parallel=40, # Run 40 requests in parallel, which is a good default\r\n)\r\ndef scrape_data(link):\r\n    # Call the scrape_html function to get the cached HTML\r\n    html = scrape_html(link)\r\n    # Extract data from the HTML using the extract_data function\r\n    return extract_data(soupify(html))\r\n\r\ndata_items = [\r\n    \"https://stackoverflow.blog/open-source\",\r\n    \"https://stackoverflow.blog/ai\",\r\n    \"https://stackoverflow.blog/productivity\",\r\n]\r\n\r\nscrape_data(data_items)\r\n```\r\n\r\nFor visiting well protected websites, use the `Browser` module. \r\n\r\nHere's a template for creating production-ready datasets using the `Browser` module:\r\n\r\n```python\r\nfrom bs4 import BeautifulSoup\r\nfrom botasaurus.task import task\r\nfrom botasaurus.browser import browser, Driver, NotFoundException\r\nfrom botasaurus.soupify import soupify\r\n\r\n@browser(\r\n    # proxy='http://username:password@datacenter-proxy-domain:proxy-port', # Uncomment to use Proxy ONLY if you face IP blocking\r\n\r\n    # block_images_and_css=True, # Uncomment to block images and CSS, which can speed up scraping\r\n    # wait_for_complete_page_load=False, # Uncomment to proceed once the DOM (Document Object Model) is loaded, without waiting for all resources to finish loading. This is recommended for faster scraping of Server Side Rendered (HTML) pages.\r\n\r\n    cache=True,\r\n    max_retry=5,  # Retry up to 5 times, which is a good default\r\n\r\n    reuse_driver= True, # Reuse the same driver for all tasks\r\n    \r\n    output=None,\r\n\r\n    close_on_crash=True,\r\n    raise_exception=True,\r\n    create_error_logs=False,\r\n)\r\ndef scrape_html(driver: Driver, link):\r\n    # Scrape the HTML and cache it\r\n    if driver.config.is_new:\r\n        driver.google_get(\r\n            link,\r\n            bypass_cloudflare=True,  # delete this line if the website you're accessing is not protected by Cloudflare\r\n        )\r\n    response = driver.requests.get(link)\r\n    \r\n    if response.status_code == 404:\r\n        # A Special Exception to skip retrying this link\r\n        raise NotFoundException(link)\r\n    response.raise_for_status()\r\n    \r\n    html = response.text        \r\n    return html\r\n\r\ndef extract_data(soup: BeautifulSoup):\r\n    # Extract the heading from the HTML\r\n    stock_name = soup.select_one('[data-testid=\"quote-hdr\"] h1').get_text()\r\n    stock_price = soup.select_one('[data-testid=\"qsp-price\"]').get_text()\r\n    \r\n    return {\r\n        \"stock_name\": stock_name,\r\n        \"stock_price\": stock_price,\r\n    }\r\n\r\n# Cache the scrape_data task as well\r\n@task(\r\n    cache=True,\r\n    close_on_crash=True,\r\n    create_error_logs=False,\r\n)\r\ndef scrape_data(link):\r\n    # Call the scrape_html function to get the cached HTML\r\n    html = scrape_html(link)\r\n    # Extract data from the HTML using the extract_data function\r\n    return extract_data(soupify(html))\r\n\r\ndata_items = [\r\n    \"https://finance.yahoo.com/quote/AAPL/\",\r\n    \"https://finance.yahoo.com/quote/GOOG/\",\r\n    \"https://finance.yahoo.com/quote/MSFT/\",\r\n]\r\n\r\nscrape_data(data_items)\r\n```\r\n\r\n### Why Am I Getting Detected on Some Websites?\r\n\r\nIf you're getting detected, it's likely due to:\r\n\r\n- Using a non-residential proxy — Services like Datadome and Cloudflare often flag datacenter IPs/VPNs.\r\n- Clicking without Human Mode enabled — Unnatural mouse movements can trigger detection.\r\n- Visiting websites too quickly — Rapid, bot-like navigation is easy to detect.\r\n\r\nTo reduce detection, follow these best practices:\r\n\r\n1. Upgrade all Botasaurus packages to the latest version:\r\n   ```bash\r\n   python -m pip install --upgrade bota botasaurus botasaurus-api botasaurus-requests botasaurus-driver botasaurus-proxy-authentication botasaurus-server botasaurus-humancursor\r\n   ```\r\n\r\n2. Enable Human Mode for human-like mouse movements:\r\n   ```python\r\n   driver.enable_human_mode()\r\n   ```\r\n\r\n3. Avoid rapid `driver.get` calls. Instead, try:\r\n   - Clicking within pages with Human Mode enabled, if possible.\r\n   - Using `driver.google_get` or `driver.get_via_this_page`.\r\n   - Using [`driver.requests.get`](https://github.com/omkarcloud/botasaurus?tab=readme-ov-file#how-to-significantly-reduce-proxy-costs-when-scraping-at-scale) to fetch the page HTML content.\r\n\r\n4. Slow down your scraper with random delays:\r\n   ```python\r\n   driver.short_random_sleep()\r\n   driver.long_random_sleep()\r\n   ```\r\n\r\n5. Avoid using `headless` mode, as it can be easily detected by Cloudflare, Datadome, and Imperva.\r\n\r\n6. Use a residential proxy, as datacenter IPs are often flagged.\r\n\r\n7. Remove the `--no-default-browser-check` argument as it is detectable by systems like Datadome, as follows:\r\n   ```python\r\n   @browser(remove_default_browser_check_argument=True)\r\n   ```\r\n\r\n8. If your IP has been flagged, you can perform this technique to change it:\r\n1. Visit [whatismyipaddress.com](https://whatismyipaddress.com/) to see your current IP Address.\r\n2. Connect your PC to a smartphone's hotspot.\r\n3. On your smartphone, turn airplane mode on and off.\r\n4. Turn the hotspot on again.\r\n5. Now visit [whatismyipaddress.com](https://whatismyipaddress.com/). You'll see a new IP address.\r\n\r\n### How Do I Close All Running Chrome Instances?\r\n\r\nWhile developing a scraper, multiple browser instances may remain open in the background (because of interrupting it with CTRL + C). This situation can cause your computer to hang.\r\n\r\n![Many Chrome processes running in Task Manager](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/chrome-running.png)\r\n\r\nTo prevent your PC from hanging, you can run the following command to close all Chrome instances:\r\n\r\n```shell\r\npython -m close_chrome\r\n```\r\n\r\n### How to Run Scraper in Docker?\r\n\r\nTo run a Scraper in Docker, use the Botasaurus Starter Template, which includes the necessary Dockerfile and Docker Compose configurations.\r\n\r\nUse the following commands to clone the Botasaurus Starter Template, build a Docker image from it, and execute the scraper within a Docker environment.\r\n\r\n```\r\ngit clone https://github.com/omkarcloud/botasaurus-starter my-botasaurus-project\r\ncd my-botasaurus-project\r\ndocker-compose build\r\ndocker-compose up\r\n```\r\n\r\n### How to Run Scraper in Gitpod?\r\n\r\nRunning a scraper in Gitpod offers several benefits:\r\n\r\n- Allows your scraper to use a powerful 8-core machine with 1000 Mbps internet speed\r\n- Makes it easy to showcase your scraper to customers without them having to install anything, by simply sharing the Gitpod machine link\r\n\r\nIn this example, we will run the Botasaurus Starter template in Gitpod:\r\n\r\n1. First, visit [this link](https://gitpod.io/#https://github.com/omkarcloud/botasaurus-starter) and sign up using your GitHub account.\r\n   \r\n   ![Screenshot (148)](https://raw.githubusercontent.com/omkarcloud/google-maps-scraper/master/screenshots/open-in-gitpod.png)\r\n  \r\n2. Once signed up, open the starter project in Gitpod.   \r\n\r\n   ![gp-continue](https://raw.githubusercontent.com/omkarcloud/assets/master/images/gitpod-continue.png)\r\n\r\n3. To use UI Scraper, run the following command in Terminal:\r\n   ```bash\r\n   python run.py\r\n   ```\r\n4. You will see a popup notification with the heading \"A service is available on port 3000\". In the popup notification, click the **\"Open Browser\"** button to open the UI Dashboard in your browser\r\n\r\n   ![open-browser.png](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/open-browser.png)\r\n\r\n5. Now, you can press the `Run` button to get the results.\r\n   \r\n   ![starter-photo.png](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/starter-photo.png)\r\n\r\nNote: Gitpod is not suitable for long-running tasks, as the environment will automatically shut down after a short period of inactivity. Use your local machine or a cloud VM for long-running scrapers.\r\n\r\n## Should I Scrape Datasets Locally or in the Cloud?  \r\nFor most scraping tasks, we recommend running the scraper **locally** on your system because:  \r\n- It requires far fewer setup steps  \r\n- It saves time and costs  \r\n- Most importantly, it allows you to quickly fix bugs and continue scraping.\r\n\r\nHowever, consider cloud scraping when:  \r\n- Running tasks longer than 5 days.  \r\n- Scraping large-scale data (terabytes).  \r\n- Performing recurring monthly scrapes.  \r\n- Having slow Internet or data caps\r\n\r\nCloud scraping is also significantly faster due to superior internet speeds (often 10x+ faster than home Wi-Fi).  \r\n\r\n---\r\n\r\n## How to Run a Data Scraper in a Virtual Machine? \r\n\r\nTo run a scraper in a virtual machine (VM), follow these steps:\r\n\r\n### 1. Prepare Your Scraper\r\n1. Use the [Botasaurus Starter Template](https://github.com/omkarcloud/botasaurus-starter) to create your dataset scraper.\r\n2. For large datasets, ensure memory efficiency (e.g., by using file formats like `ndjson`).  \r\n3. Add project dependencies to `requirements.txt`.\r\n4. Push your project to GitHub.\r\n\r\n### 2. Set Up a Virtual Machine\r\n\r\n1. If you don't already have one, create a Google Cloud Account. You'll receive a $300 credit to use over 3 months.\r\n   ![Select-your-billing-country](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/Select-your-billing-country.png)\r\n\r\n\r\n2. Go to [Google Click to Deploy](https://console.cloud.google.com/marketplace/product/click-to-deploy-images/nodejs), create a deployment and configure it as follows or as appropriate based on your workload:\r\n   ```\r\n   Zone: us-central1-a # Use us-central1 (Iowa) for the lowest-cost VMs\r\n   Series: N1\r\n   Machine Type: n1-standard-2 (2 vCPU, 1 core, 7.5 GB memory)\r\n   Boot Disk Type: Standard persistent disk\t# This is the most cost-effective disk option.\r\n   Boot disk size in GB: 20 GB # Adjust based on storage needs  \r\n   ```\r\n   ![Deployment setup](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/deploy-node-vm.gif)\r\n\r\n3. Navigate to [VM Instances](https://console.cloud.google.com/compute/instances) and click the SSH button to SSH into the VM.\r\n   ![ssh-vm](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/ssh-vm.png)\r\n\r\n4.  Now, run the following command in the terminal and wait for it to complete:\r\n\r\n   ```bash\r\n   curl -sL https://raw.githubusercontent.com/omkarcloud/botasaurus/master/vm-scripts/install-bota.sh | bash\r\n   ```\r\n   \r\n5.  Install your scraper by running the following command. It may take 5 minutes to install the scraper, so grab a coffee or watch [this awesome video](https://www.youtube.com/watch?v=nwAYpLVyeFU):\r\n\r\n   ```bash\r\n   python3 -m bota install-scraper --repo-url https://github.com/omkarcloud/botasaurus-starter\r\n   ```\r\n   ![install-scraper](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/install-scraper-vm.gif)\r\n\r\n   Note: If you are using a different repo, replace `https://github.com/omkarcloud/botasaurus-starter` with your repo URL.\r\n   \r\nThat's it! You have successfully installed the scraper in a virtual machine. The scraper will now start running and succeed.\r\n\r\n![ls-output](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/vm-succeed.png)\r\n\r\n*Notes*\r\n- The `main.py` file serves as the scraper's entry point.\r\n- Update your project's `requirements.txt` to ensure it has all the dependencies required to run the scraper.\r\n- Ensure your VM has enough memory for your scraper's needs.\r\n- If running a headful browser, enable a virtual display by setting `enable_xvfb_virtual_display=True`. This creates a virtual display required for running a headful browser in a VM.\r\n  ```python\r\n  @browser(enable_xvfb_virtual_display=True)\r\n  ```\r\n- The scraper will run until it completes successfully or fails three times. You can also configure retries as follows:\r\n  For example, to allow a maximum of 5 retries:\r\n  ```bash\r\n  python3 -m bota install-scraper --repo-url \u003cyour-repo\u003e --max-retry=5\r\n  ```\r\n  or, to allow unlimited retries:\r\n  ```bash\r\n  python3 -m bota install-scraper --repo-url \u003cyour-repo\u003e --max-retry=unlimited\r\n  ```\r\n- If your scraper fails, you can check the logs of the current boot by running:\r\n  ```bash\r\n  journalctl -u botasaurus-starter.service -b # replace botasaurus-starter with your repo name\r\n  ```\r\n\r\n*Downloading Data*\r\nTo download the scraped data, you can either:\r\n1. Download it directly from the VM.\r\n![Download Data from VM](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/download-data-vm.png)\r\n2. Upload it to Amazon S3:\r\n   ```python\r\n   from botasaurus import bt\r\n   bt.upload_to_s3('data.json', 'my-bucket', \"AWS_ACCESS_KEY\", \"AWS_SECRET_KEY\")\r\n   ```\r\n\r\n## How to Stop the Scraper\r\nIf you are performing recurring monthly scrapes, it's best to stop the scraper instead of deleting it. Note that you will only incur storage costs (~$0.4 per month for a 10GB Standard Persistent Disk) but not compute costs.\r\n\r\nTo stop the scraper:\r\n1. Visit [VM Instances](https://console.cloud.google.com/compute/instances).\r\n2. Select your VM and stop it.\r\n\r\n![stop-scraper-in-vm](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/stop-scraper-in-vm.png)\r\n\r\nTo restart later:\r\n1. Start the VM from [VM Instances](https://console.cloud.google.com/compute/instances).\r\n\r\n![run-scraper-in-vm](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/run-scraper-in-vm.png)\r\n2. SSH into it.\r\n3. Delete caches and update sitemaps if needed.\r\n4. Restart with:\r\n   ```bash\r\n   shutdown -r now\r\n   ```\r\n\r\n## How to Delete the Scraper and Avoid Incurring Further Charges\r\n\r\nIf you no longer need the scraper, please ensure you have downloaded your data before deleting it.\r\n\r\nNext, follow these steps to delete the scraper:\r\n1. Go to [Deployments](https://console.cloud.google.com/products/solutions/deployments).\r\n2. Delete your deployment.\r\n\r\n![Delete deployment](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/delete-deployment.png)\r\n\r\nThat's it! You have successfully deleted the scraper, and you will not incur any disk or compute charges.\r\n\r\n## How to Run a UI Scraper in a Virtual Machine\r\n\r\nTo run your scraper in a virtual machine, we will:\r\n- Create a static IP\r\n- Create a VM with that IP\r\n- SSH into the VM\r\n- Install the scraper\r\n\r\nNow, follow these steps to run your scraper in a virtual machine:\r\n\r\n1. Create a Google Cloud Account if you don't already have one.\r\n   ![Select-your-billing-country](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/Select-your-billing-country.png)\r\n\r\n2. Visit the [Google Cloud Console](https://console.cloud.google.com/welcome?cloudshell=true) and click the Cloud Shell button. A terminal will open up.\r\n   ![click-cloud-shell-btn](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/click-cloud-shell-btn.png)\r\n\r\n3. Run the following commands in the terminal:\r\n\r\n   ```bash\r\n   python -m pip install bota\r\n   python -m bota create-ip\r\n   ```\r\n\r\n   You will be asked for a VM name. Enter any name you like, such as \"pikachu\".\r\n\r\n   \u003e Name: pikachu\r\n\r\n   Then, you will be asked for the region for the scraper. Press Enter to go with the default, which is \"us-central1\", as it has the lowest-cost VMs.\r\n\r\n   \u003e Region: Default\r\n\r\n   ![Install bota](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/install-bota.gif)\r\n\r\n4. Go to [Google Click to Deploy](https://console.cloud.google.com/marketplace/product/click-to-deploy-images/nodejs), create a deployment and configure it as follows or as appropriate based on your workload:\r\n   ```\r\n   Zone: us-central1-a # Use the zone from the region you selected in the previous step.\r\n   Series: N1\r\n   Machine Type: n1-standard-2 (2 vCPU, 1 core, 7.5 GB memory)\r\n   Boot Disk Type: Standard persistent disk\t# This is the most cost-effective disk option.\r\n   Boot disk size in GB: 20 GB # Adjust based on storage needs  \r\n   Network Interface [External IP]: pikachu-ip # Use the IP name you created in the previous step.\r\n   ```\r\n   ![deploy-node](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/deploy-node.gif)\r\n\r\n5. Navigate to [VM Instances](https://console.cloud.google.com/compute/instances) and click the SSH button to SSH into the VM.\r\n   ![ssh-vm](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/ssh-vm.png)\r\n\r\n6.  Now, run the following command in the terminal:\r\n\r\n   ```bash\r\n   curl -sL https://raw.githubusercontent.com/omkarcloud/botasaurus/master/vm-scripts/install-bota.sh | bash\r\n   ```\r\n\r\n7. Finally, install the UI scraper by running the following command, then wait for 5 minutes for it to complete:\r\n   ```bash\r\n   python3 -m bota install-ui-scraper --repo-url https://github.com/omkarcloud/botasaurus-starter\r\n   ```\r\n   \r\n   ![install-scraper](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/install-scraper.gif)\r\n   Note: If you are using a different repo, replace `https://github.com/omkarcloud/botasaurus-starter` with your repo URL.\r\n\r\nThat's it! You have successfully launched the scraper in a virtual machine. When the previous commands are done, you will see a **link** to your scraper. Visit it to run your scraper.\r\n\r\n![vm-success](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/vm-success.gif)\r\n\r\n*Notes*\r\n    - Update your project's `requirements.txt` to ensure it has all the dependencies required to run the scraper.\r\n    - Ensure your VM has enough memory for your scraper's needs.\r\n    - If running a headful browser, enable a virtual display by setting `enable_xvfb_virtual_display=True`. This creates a virtual display required for running a headful browser in a VM.\r\n  ```python\r\n  @browser(enable_xvfb_virtual_display=True)\r\n  ```\r\n    - The UI scraper will run indefinitely and will be available at the printed link.\r\n    - If your UI scraper fails, you can check the logs of the current boot by running:\r\n  ```bash\r\n  journalctl -u backend.service -b \r\n  ```\r\n\r\n##  How to Delete the UI Scraper and Avoid Incurring Further Charges\r\n\r\nIf you no longer need the scraper, please ensure you have downloaded your data before deleting it.\r\n\r\nNext, follow these steps to delete the scraper:\r\n\r\n1. Delete the static IP by running the following command:\r\n\r\n   ```bash\r\n   python -m bota delete-ip\r\n   ```\r\n\r\n   You will be asked for the name of the VM you created in the first step. Enter the name and press Enter.\r\n\r\n   ![Delete IP](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/delete-ip.png)\r\n\r\n   Note: If you forgot the name of the IP, you can also delete all the IPs by running `python -m bota delete-all-ips`.\r\n\r\n2. Go to [Deployments](https://console.cloud.google.com/products/solutions/deployments) and delete your deployment.\r\n\r\n   ![Delete deployment](https://raw.githubusercontent.com/omkarcloud/botasaurus/master/images/delete-deployment.png)\r\n\r\nThat's it! You have successfully deleted the scraper, and you will not incur any further charges.\r\n\r\n### How to Run Scraper in Kubernetes?\r\nVisit [this link](https://github.com/omkarcloud/botasaurus/blob/master/run-scraper-in-kubernetes.md) to learn how to run scraper at scale using Kubernetes.\r\n\r\n\r\n### I have a feature request!\r\n\r\nWe'd love to hear it! Share them on [GitHub Discussions](https://github.com/omkarcloud/botasaurus/discussions). \r\n\r\n[![Make](https://raw.githubusercontent.com/omkarcloud/google-maps-scraper/master/screenshots/ask-on-github.png)](https://www.omkar.cloud/l/botasaurus-make-discussions/)\r\n\u003c!-- \r\n### Do you have a Discord community?\r\n\r\nYes, we have a Discord community where you can connect with other developers, ask questions, and share your experiences. Join our Discord community [here](https://discord.com/invite/rw9VeyuSM8). --\u003e\r\n\r\n### ❓ Advanced Questions\r\n\r\nCongratulations on completing the Botasaurus Documentation! Now, you have all the knowledge needed to effectively use Botasaurus. \r\n\r\nYou may choose to read the following questions based on your interests:\r\n\r\n1. [How to Run Botasaurus in Google Colab?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-to-run-botasaurus-in-google-colab)\r\n\r\n2. [How can I allow users to filter the scraped data?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-can-i-allow-users-to-filter-the-scraped-data)\r\n\r\n3. [How can I allow the user to sort the scraped data?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-can-i-allow-the-user-to-sort-the-scraped-data)\r\n\r\n4. [How can I present the scraped data in different views?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-can-i-present-the-scraped-data-in-different-views)\r\n\r\n5. [When building a large dataset, customers often request data in different formats like overview and review. How can I do that?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#when-building-a-large-dataset-customers-often-request-data-in-different-formats-like-overview-and-review-how-can-i-do-that)\r\n\r\n6. [What more can I configure when adding a scraper?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#what-more-can-i-configure-when-adding-a-scraper)\r\n\r\n7. [How to control the maximum number of browsers and requests running at any point of time?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-to-control-the-maximum-number-of-browsers-and-requests-running-at-any-point-of-time)\r\n\r\n8. [How do I change the title, header title, and description of the scraper?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-do-i-change-the-title-header-title-and-description-of-the-scraper)\r\n\r\n9. [How can I use a database like PostgreSQL with UI Scraper?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-can-i-use-a-database-like-postgresql-with-ui-scraper)\r\n\r\n10. [Which PostgreSQL provider should I choose among Supabase, Google Cloud SQL, Heroku, and Amazon RDS?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#which-postgresql-provider-should-i-choose-among-supabase-google-cloud-sql-heroku-and-amazon-rds)\r\n\r\n11. [How to create a PostgreSQL database on Supabase?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-to-create-a-postgresql-database-on-supabase)\r\n\r\n12. [How to create a PostgreSQL database on Google Cloud?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#how-to-create-a-postgresql-database-on-google-cloud)\r\n\r\n13. [I am a Youtuber, Should I create YouTube videos about Botasaurus? If so, how can you help me?](https://github.com/omkarcloud/botasaurus/blob/master/advanced.md#i-am-a-youtuber-should-i-create-youtube-videos-about-botasaurus-if-so-how-can-you-help-me)\r\n\r\n## Thank You\r\n- To That, who has given me a sufficiently intelligent mind to create Botasaurus and do a lot of good.\r\n- I made Botasaurus because I would be really happy if you could use it to successfully complete your project. So, a Gigantic Thank you for using Botasaurus!\r\n- A heartfelt thank you to [Cheng Zhao](https://zcbenz.com/) from GitHub for creating Electron, which powers Botasaurus Desktop.\r\n- Kudos to the Apify Team for creating the `proxy-chain` library. The implementation of SSL-based Proxy Authentication wouldn't have been possible without their groundbreaking work on `proxy-chain`.\r\n- Shout out to [ultrafunkamsterdam](https://github.com/ultrafunkamsterdam) for creating `nodriver`, which inspired the creation of Botasaurus Driver.\r\n- A big thank you to [daijro](https://github.com/daijro) for creating [hrequest](https://github.com/daijro/hrequests), which inspired the creation of botasaurus-requests.\r\n- Deepest gratitude to [Flori Batusha](https://github.com/riflosnake) and [Ambri](https://github.com/iLeaf30/) for their contributions in creating **Botasaurus Humancursor**, which brings human-like mouse movements to Botasaurus.\r\n- A humongous thank you to Cloudflare, DataDome, Imperva, and all bot recognition systems. Had you not been there, we wouldn't be either 😅.\r\n\r\n*Now, what are you waiting for? 🤔 Go and make something mastastic! 🚀*\r\n\r\n## Love It? [Star It! ⭐](https://github.com/omkarcloud/botasaurus)\r\n\r\nBecome one of our amazing stargazers by giving us a star ⭐ on GitHub!\r\n\r\nIt's just one click, but it means the world to me.\r\n\r\n\u003ca href=\"https://github.com/omkarcloud/botasaurus/stargazers\"\u003e\r\n    \u003cimg src=\"https://bytecrank.com/nastyox/reporoster/php/stargazersSVG.php?user=omkarcloud\u0026repo=botasaurus\" alt=\"Stargazers for @omkarcloud/botasaurus\"\u003e\r\n\u003c/a\u003e\r\n\r\n\r\n## Disclaimer for Botasaurus Project\r\n\r\n\u003e By using Botasaurus, you agree to comply with all applicable local and international laws related to data scraping, copyright, and privacy. The developers of Botasaurus are not responsible for any misuse of this software. It is the sole responsibility of the user to ensure adherence to all relevant laws regarding data scraping, copyright, and privacy, and to use Botasaurus in an ethical and legal manner.\r\n\r\nWe take the concerns of the Botasaurus Project very seriously. For any inquiries or issues, please contact Chetan Jain at [chetan@omkar.cloud](mailto:chetan@omkar.cloud). We will take prompt and necessary action in response to your emails.\r\n\r\n","funding_links":["https://github.com/sponsors/Chetan11-Dev"],"categories":["Python","🥷 Stealth \u0026 Anti-Detection"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomkarcloud%2Fbotasaurus","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fomkarcloud%2Fbotasaurus","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fomkarcloud%2Fbotasaurus/lists"}