[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"project-72180":3},{"id":4,"name":5,"fullName":6,"owner":7,"repo":5,"description":8,"homepage":9,"htmlUrl":9,"language":10,"languages":9,"totalLinesOfCode":9,"stars":11,"forks":12,"watchers":13,"openIssues":14,"contributorsCount":15,"subscribersCount":15,"size":15,"stars1d":16,"stars7d":17,"stars30d":18,"stars90d":15,"forks30d":15,"starsTrendScore":19,"compositeScore":20,"rankGlobal":9,"rankLanguage":9,"license":21,"archived":22,"fork":22,"defaultBranch":23,"hasWiki":22,"hasPages":24,"topics":25,"createdAt":9,"pushedAt":9,"updatedAt":26,"readmeContent":27,"aiSummary":28,"trendingCount":15,"starSnapshotCount":15,"syncStatus":29,"lastSyncTime":30,"discoverSource":31},72180,"nodriver","ultrafunkamsterdam\u002Fnodriver","ultrafunkamsterdam","Successor of Undetected-Chromedriver. Providing a blazing fast framework for web automation, webscraping, bots and any other creative ideas which are normally hindered by annoying anti bot systems like Captcha \u002F CloudFlare \u002F Imperva \u002F hCaptcha ",null,"Python",4340,410,90,8,0,22,47,141,66,29.84,"GNU Affero General Public License v3.0",false,"main",true,[],"2026-06-12 02:02:59","\n\n\n\nNODRIVER\n=======================\n\n### nodriver provides next level async webscraping and browser automation library for python with an easy interface which Just Makes Sense ™\n\n\n* **This is the official successor of the** [Undetected-Chromedriver](https:\u002F\u002Fgithub.com\u002Fultrafunkamsterdam\u002Fundetected-chromedriver\u002F) **python package.**\n* **No more webdriver, no more selenium**\n\n### for docs click [here](https:\u002F\u002Fultrafunkamsterdam.github.io\u002Fnodriver)\n\nDirect communication provides even better resistance against web applicatinon firewalls (WAF’s), while\nperformance gets a massive boost.\nThis module is, contrary to undetected-chromedriver, fully asynchronous.\n\nWhat makes this package different from other known packages,\nis the optimization to stay undetected for most anti-bot solutions.\n\nAnother focus point is usability and quick prototyping, so expect a lot to work `-as is-` ,\nwith most method parameters having `best practice` defaults.\nUsing 1 or 2 lines, this is up and running, providing best practice config\nby default. It cleans up created files (profile) afterwards.\n\nknown to work with \n- chromium\n- chrome\n- edge\n- brave \n\n\nWhile usability and convenience is important. It’s also easy\nto fully customizable everything using the entire array of\n[CDP](https:\u002F\u002Fchromedevtools.github.io\u002Fdevtools-protocol\u002F) domains, methods and events available.\n\n### Some features\n* No chromedriver binary or Selenium dependency\n* Up and running in 1 line of code\\*\n* uses fresh profile on each run, cleans up on exit\n* save and load cookies to file to not repeat tedious login steps\n* ```tab.find(\"sometext\")```\n\n  ```tab.find_all(\"sometext\")```\n\n  ```tab.select(\"a[class*=something]\")```\n\n  ```tab.select_all(\"a[href] > div > img\")```\n\n  smart and performant element lookup, by selector or text, including iframe content.\n  this could also be used as wait condition for a element to appear, since it will retry\n  for the duration of \u003Ctimeout> until found. so an ```await tab.select('body')``` could be used\n  as an indicator whether a page is loaded.\n  the find method searches by text, but will not naively return the first \n  matching element, but will match candidates by closest matching text length (shortest wins),\n  this makes lookups like tab.find('accept all') return the actual cookie button instead of\n  a script in the headers\n\n* can connect to a running chrome debug session\n* descriptive \\_\\_repr_\\_ for elements, which represent the element as html\n* utility function to convert a running undetected_chromedriver.Chrome instance\n  to a nodriver.Browser instance and contintue from there\n* packed with helpers and utility methods for most used and important operations\n\nwhat is new\n--------------------\n\n### 0.50.1 switch to flat mode connection.\n\nParts are rewritten to use flat connections in the protocol.\nWhy?\n    - iframes are included in most operations.\n    - tab got a new method: `await tab.get_frames()`\n    which will return Iframes that are inspectable.\n    - find() will include iframes, so you can even search for \"verify you are human\" and\n    click the verification checkbox in js challenges.\n\nSince this required quite some rewriting, please test thoroughly, especially if you run large projects.\n\n\u003Cvideo autoplay loop muted playsinline src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002Fb30872f0-62fe-45b2-a67a-adc1e0c7c048\">\u003C\u002Fvideo>\n\n\n### ```tab.xpath(selector, timeout=2.5)```\nfind nodes using xpath selector!\nsee [tab xpath in the api docs](https:\u002F\u002Fultrafunkamsterdam.github.io\u002Fnodriver\u002Fnodriver\u002Fclasses\u002Ftab.html#nodriver.Tab.xpath)\n\n\n### ```tab.cf_verify()```\nfinds the checkbox and click it successfully\nthis only works when NOT in expert mode.\ncurrently built-in english only\nrequires opencv-python package to be installed\n\n\u003Cvideo autoplay loop muted playsInline src=\"https:\u002F\u002Fgithub.com\u002Fuser-attachments\u002Fassets\u002F288c5e01-39c5-4453-9e64-2b40c3a8548d\">\u003C\u002Fvideo>\n\n\n### ```tab.bypass_insecure_connection_warning()```\nconvenience method, for insecure page warning.\nfor example when a certificate is invalid.\n\n### ```tab.open_external_debugger()```\nlets you inspect the tab without breaking your connection\n\n### ```tab.get_local_storage()```\nget localstorage content\n\n\n### ```tab.set_local_storage(dict)```\nset localstorage content\n\n### ```tab.add_handler(someEvent, callback)```\ncallback may accept a single argument (event), or 2 arguments (event, tab).\n\n\n### ```start(expert=True)```\ndoes some hacking for more experienced users. It disables \nweb security and origin-trials, as well as ensures \nshadow-roots are always open. This makes you more detectable though!\n\n\nInstallation\n=================\n\n**you need chrome (or some chromium based browser) installed\n preferably in the default location\n on the machine where you use this package.**\n\nwhen running on a headless machine, like AWS or any other environment where\nno display is present, it's best to use some **Xvfb** tool, to emulate a **screen**. \nalternatively this package can be used in headless mode.\n\n\n\n#### you can use pip to install nodriver\n\n```default\npip install nodriver\n```\n\nTo update\n---------\n\n```default\npip install -U nodriver\n```\n\n\n\u003Ca id=\"getting-started-commands\">\u003C\u002Fa>\n\nusage examples\n=======================\n\nThe aim of this project (just like undetected-chromedriver, somewhere long ago)\nis to keep it short and simple, so you can quickly open an editor or interactive session,\ntype or paste a few lines and off you go.\n\nsimple \n--------------\n\n```python\nimport nodriver as uc\n\nasync def main():\n\n    browser = await uc.start()\n    page = await browser.get('https:\u002F\u002Fwww.nowsecure.nl')\n\n    ... further code ...\n\nif __name__ == '__main__':\n    # since asyncio.run never worked (for me)\n    uc.loop().run_until_complete(main())\n```\n\n\nCustom starting options\n---------------------\nI’ll leave out the async boilerplate here\n\n```python\nfrom nodriver import *\n\nbrowser = await start(\n    headless=False,\n    user_data_dir=\"\u002Fpath\u002Fto\u002Fexisting\u002Fprofile\",  # by specifying it, it won't be automatically cleaned up when finished\n    browser_executable_path=\"\u002Fpath\u002Fto\u002Fsome\u002Fother\u002Fbrowser\",\n    browser_args=['--some-browser-arg=true', '--some-other-option'],\n    lang=\"en-US\"   # this could set iso-language-code in navigator, not recommended to change\n)\ntab = await browser.get('https:\u002F\u002Fsomewebsite.com')\n```\n\nCustom options using the Config object\n---------------------------\n\nI’ll leave out the async boilerplate here\n\n```python \nfrom nodriver import *\n\nconfig = Config()\nconfig.headless = False\nconfig.user_data_dir=\"\u002Fpath\u002Fto\u002Fexisting\u002Fprofile\",  # by specifying it, it won't be automatically cleaned up when finished\nconfig.browser_executable_path=\"\u002Fpath\u002Fto\u002Fsome\u002Fother\u002Fbrowser\",\nconfig.browser_args=['--some-browser-arg=true', '--some-other-option'],\nconfig.lang=\"en-US\"   # this could set iso-language-code in navigator, not recommended to change\n)\n```\n\nsome impression\n----\n```python \nimport nodriver\n\nasync def main():\n\n    browser = await nodriver.start()\n    page = await browser.get('https:\u002F\u002Fwww.nowsecure.nl')\n\n    await page.save_screenshot()\n    await page.get_content()\n    await page.scroll_down(150)\n    elems = await page.select_all('*[src]')\n\n    for elem in elems:\n        await elem.flash()\n\n    page2 = await browser.get('https:\u002F\u002Ftwitter.com', new_tab=True)\n    page3 = await browser.get('https:\u002F\u002Fgithub.com\u002Fultrafunkamsterdam\u002Fnodriver', new_window=True)\n\n    for p in (page, page2, page3):\n       await p.bring_to_front()\n       await p.scroll_down(200)\n       await p   # wait for events to be processed\n       await p.reload()\n       if p != page3:\n           await p.close()\n\nif __name__ == '__main__':\n\n    # since asyncio.run never worked (for me)\n    uc.loop().run_until_complete(main())\n```\n\n\n\ncomplete code example\n-----------------------------\nautomating Twitter\u002FX account creation\n\n```python\n\nA more concrete example, which can be found in the .\u002Fexample\u002F folder,\nshows a script to create a twitter account\n\n```python\nimport random\nimport string\nimport logging\n\nlogging.basicConfig(level=30)\n\nimport nodriver as uc\n\nmonths = [\n    \"january\",\n    \"february\",\n    \"march\",\n    \"april\",\n    \"may\",\n    \"june\",\n    \"july\",\n    \"august\",\n    \"september\",\n    \"october\",\n    \"november\",\n    \"december\",\n]\n\n\nasync def main():\n    driver = await uc.start()\n\n    tab = await driver.get(\"https:\u002F\u002Ftwitter.com\")\n\n    # wait for text to appear instead of a static number of seconds to wait\n    # this does not always work as expected, due to speed.\n    print('finding the \"create account\" button')\n    create_account = await tab.find(\"create account\", best_match=True)\n\n    print('\"create account\" => click')\n    await create_account.click()\n\n    print(\"finding the email input field\")\n    email = await tab.select(\"input[type=email]\")\n\n    # sometimes, email field is not shown, because phone is being asked instead\n    # when this occurs, find the small text which says \"use email instead\"\n    if not email:\n        use_mail_instead = await tab.find(\"use email instead\")\n        # and click it\n        await use_mail_instead.click()\n\n        # now find the email field again\n        email = await tab.select(\"input[type=email]\")\n\n    randstr = lambda k: \"\".join(random.choices(string.ascii_letters, k=k))\n\n    # send keys to email field\n    print('filling in the \"email\" input field')\n    await email.send_keys(\"\".join([randstr(8), \"@\", randstr(8), \".com\"]))\n\n    # find the name input field\n    print(\"finding the name input field\")\n    name = await tab.select(\"input[type=text]\")\n\n    # again, send random text\n    print('filling in the \"name\" input field')\n    await name.send_keys(randstr(8))\n\n    # since there are 3 select fields on the tab, we can use unpacking\n    # to assign each field\n    print('finding the \"month\" , \"day\" and \"year\" fields in 1 go')\n    sel_month, sel_day, sel_year = await tab.select_all(\"select\")\n\n    # await sel_month.focus()\n    print('filling in the \"month\" input field')\n    await sel_month.send_keys(months[random.randint(0, 11)].title())\n\n    # await sel_day.focus()\n    # i don't want to bother with month-lengths and leap years\n    print('filling in the \"day\" input field')\n    await sel_day.send_keys(str(random.randint(0, 28)))\n\n    # await sel_year.focus()\n    # i don't want to bother with age restrictions\n    print('filling in the \"year\" input field')\n    await sel_year.send_keys(str(random.randint(1980, 2005)))\n\n    await tab\n\n    # let's handle the cookie nag as well\n    cookie_bar_accept = await tab.find(\"accept all\", best_match=True)\n    if cookie_bar_accept:\n        await cookie_bar_accept.click()\n\n    await tab.sleep(1)\n\n    next_btn = await tab.find(text=\"next\", best_match=True)\n    # for btn in reversed(next_btns):\n    await next_btn.mouse_click()\n\n    print(\"sleeping 2 seconds\")\n    await tab.sleep(2)  # visually see what part we're actually in\n\n    print('finding \"next\" button')\n    next_btn = await tab.find(text=\"next\", best_match=True)\n    print('clicking \"next\" button')\n    await next_btn.mouse_click()\n\n    # just wait for some button, before we continue\n    await tab.select(\"[role=button]\")\n\n    print('finding \"sign up\"  button')\n    sign_up_btn = await tab.find(\"Sign up\", best_match=True)\n    # we need the second one\n    print('clicking \"sign up\"  button')\n    await sign_up_btn.click()\n\n    print('the rest of the \"implementation\" is out of scope')\n    # further implementation outside of scope\n    await tab.sleep(10)\n    driver.stop()\n\n    # verification code per mail\n\n\nif __name__ == \"__main__\":\n    # since asyncio.run never worked (for me)\n    # i use\n    uc.loop().run_until_complete(main())\n\n```\n\n### more examples in the .\u002Fexample\u002F folder\n","nodriver 是一个用于网页自动化、数据抓取和机器人开发的高级异步框架，特别针对那些通常被反爬虫系统如Captcha、CloudFlare等阻碍的应用场景。它摒弃了传统的webdriver和selenium依赖，采用直接通信方式，不仅显著提升了性能，还增强了对各种Web应用防火墙的抵抗力。此库完全异步化设计，并且支持多种浏览器如Chrome、Edge等。nodriver强调易用性和快速原型构建，只需几行代码即可启动，同时提供丰富的自定义选项以适应更复杂的需求。其核心特性包括智能元素查找、自动清理临时文件、保存和加载Cookies等功能，非常适合需要绕过反爬机制进行高效网络操作的开发者使用。",2,"2026-06-11 03:40:43","high_star"]