
Undetected-Chromedriver의 후속작입니다. Captcha / CloudFlare / Imperva / hCaptcha와 같은 성가신 안티 봇 시스템에 의해 일반적으로 방해받는 웹 자동화, 웹스크래핑, 봇 및 기타 창의적인 아이디어를 위한 초고속 프레임워크를 제공합니다.
직접 통신은 웹 애플리케이션 방화벽(WAF)에 대해 훨씬 더 강한 저항성을 제공하며, 성능도 대폭 향상됩니다. 이 모듈은 undetected-chromedriver와 달리 완전히 비동기(async) 방식입니다.
이 패키지를 다른 알려진 패키지들과 차별화하는 점은 대부분의 안티봇(anti-bot) 솔루션에서 탐지되지 않도록 최적화되어 있다는 것입니다.
또 다른 핵심 포인트는 사용성과 빠른 프로토타이핑입니다. 따라서 대부분의 메서드 매개변수에 모범 사례(best practice) 기본값이 설정되어 있어 많은 것들이 -있는 그대로- 작동할 것으로 기대하세요.
1~2줄이면 실행되며, 기본적으로 모범 사례 구성을 제공합니다. 작업 후에는 생성된 파일(프로필)을 정리합니다.
다음에서 작동하는 것으로 알려져 있습니다.
사용성과 편의성도 중요하지만, CDP 도메인, 메서드, 이벤트의 전체 배열을 사용하여 모든 것을 완전히 사용자 정의하는 것도 쉽습니다.
chromedriver 바이너리나 Selenium 의존성 없음
코드 한 줄로 바로 실행 가능*
실행할 때마다 새 프로필을 사용하고, 종료 시 정리함
쿠키를 파일로 저장하고 불러와서 번거로운 로그인 절차를 반복하지 않음
tab.find("sometext")
tab.find_all("sometext")
tab.select("a[class*=something]")
tab.select_all("a[href] > div > img")
셀렉터 또는 텍스트로 요소를 찾는 스마트하고 효율적인 요소 검색을 제공하며, iframe 콘텐츠도 포함합니다.
이 기능은 요소가 나타날 때까지 시간 동안 재시도하므로, 요소가 나타나기를 기다리는 조건으로도 사용할 수 있습니다. 따라서 await tab.select('body')는 페이지 로드 여부를 나타내는 지표로 사용될 수 있습니다.
find 메서드는 텍스트로 검색하지만, 단순히 첫 번째 일치 요소를 반환하지 않고 텍스트 길이가 가장 가까운 후보를 매칭합니다(가장 짧은 텍스트가 우선). 이렇게 하면 tab.find('accept all') 같은 조회가 헤더의 스크립트를 반환하는 대신 실제 쿠키 버튼을 반환하게 됩니다.
실행 중인 chrome 디버그 세션에 연결 가능
요소를 html로 표현하는 설명적인 __repr__ 제공
실행 중인 undetected_chromedriver.Chrome 인스턴스를 nodriver.Browser 인스턴스로 변환하여 그대로 계속 작업할 수 있는 유틸리티 함수 제공
가장 많이 사용되고 중요한 작업을 위한 헬퍼 및 유틸리티 메서드가 가득 포함됨
프로토콜에서 플랫 연결을 사용하도록 일부 파트가 다시 작성되었습니다.
왜?
- 대부분의 작업에 iframe이 포함됩니다.
- 탭에 새로운 메서드가 추가되었습니다: await tab.get_frames()
검사 가능한 Iframe을 반환합니다.
- find()는 iframe을 포함하므로 "verify you are human"을 검색하고
js 챌린지의 인증 체크박스를 클릭할 수도 있습니다.
이번 변경은 상당한 재작성이 필요했으므로, 특히 대규모 프로젝트를 운영 중이라면 철저히 테스트해 주세요.
tab.xpath(selector, timeout=2.5)xpath 셀렉터를 사용하여 노드를 찾습니다! api 문서의 tab xpath를 참조하세요.
tab.cf_verify()체크박스를 찾아 성공적으로 클릭합니다. 이 기능은 expert 모드가 아닐 때만 작동합니다. 현재는 영어만 내장되어 있습니다. opencv-python 패키지가 설치되어 있어야 합니다.
tab.bypass_insecure_connection_warning()안전하지 않은 페이지 경고를 처리하기 위한 편의 메서드입니다. 예를 들어 인증서가 유효하지 않은 경우입니다.
tab.open_external_debugger()연결을 끊지 않고 탭을 검사할 수 있게 해 줍니다.
tab.get_local_storage()localstorage 콘텐츠 가져오기
tab.set_local_storage(dict)localstorage 콘텐츠 설정
tab.add_handler(someEvent, callback)callback은 단일 인자(event) 또는 2개의 인자(event, tab)를 받을 수 있습니다.
start(expert=True)보다 숙련된 사용자를 위한 몇 가지 해킹을 수행합니다. 웹 보안과 origin-trials를 비활성화하고, shadow-roots가 항상 열려 있도록 보장합니다. 다만 이렇게 하면 탐지될 가능성이 더 높아집니다!
이 패키지를 사용하는 머신에 chrome(또는 일부 chromium 기반 브라우저)이 설치되어 있어야 하며, 가급적 기본 위치에 설치하는 것이 좋습니다.
디스플레이가 없는 AWS 또는 기타 환경과 같은 헤드리스 머신에서 실행할 때는 **화면(screen)**을 에뮬레이션하기 위해 Xvfb 도구를 사용하는 것이 가장 좋습니다. 대안으로 이 패키지는 헤드리스 모드로도 사용할 수 있습니다.
pip install nodriver
pip install -U nodriver
이 프로젝트의 목표는 (아주 오래전 undetected-chromedriver와 마찬가지로) 짧고 단순하게 유지하는 것입니다. 그래서 에디터나 인터랙티브 세션을 빠르게 열고, 몇 줄을 입력하거나 붙여넣으면 바로 실행할 수 있습니다.
import nodriver as uc
async def main():
browser = await uc.start()
page = await browser.get('https://www.nowsecure.nl')
... further code ...
if __name__ == '__main__':
# since asyncio.run never worked (for me)
uc.loop().run_until_complete(main())
여기서는 async 보일러플레이트는 생략하겠습니다.
from nodriver import *
browser = await start(
headless=False,
user_data_dir="/path/to/existing/profile", # by specifying it, it won't be automatically cleaned up when finished
browser_executable_path="/path/to/some/other/browser",
browser_args=['--some-browser-arg=true', '--some-other-option'],
lang="en-US" # this could set iso-language-code in navigator, not recommended to change
)
tab = await browser.get('https://somewebsite.com')
여기서는 async 보일러플레이트는 생략하겠습니다.
from nodriver import *
config = Config()
config.headless = False
config.user_data_dir="/path/to/existing/profile", # by specifying it, it won't be automatically cleaned up when finished
config.browser_executable_path="/path/to/some/other/browser",
config.browser_args=['--some-browser-arg=true', '--some-other-option'],
config.lang="en-US" # this could set iso-language-code in navigator, not recommended to change
)
import nodriver
async def main():
browser = await nodriver.start()
page = await browser.get('https://www.nowsecure.nl')
await page.save_screenshot()
await page.get_content()
await page.scroll_down(150)
elems = await page.select_all('*[src]')
for elem in elems:
await elem.flash()
page2 = await browser.get('https://twitter.com', new_tab=True)
page3 = await browser.get('https://github.com/ultrafunkamsterdam/nodriver', new_window=True)
for p in (page, page2, page3):
await p.bring_to_front()
await p.scroll_down(200)
await p # wait for events to be processed
await p.reload()
if p != page3:
await p.close()
if __name__ == '__main__':
# since asyncio.run never worked (for me)
uc.loop().run_until_complete(main())
Twitter/X 계정 생성 자동화
A more concrete example, which can be found in the ./example/ folder,
shows a script to create a twitter account
```python
import random
import string
import logging
logging.basicConfig(level=30)
import nodriver as uc
months = [
"january",
"february",
"march",
"april",
"may",
"june",
"july",
"august",
"september",
"october",
"november",
"december",
]
async def main():
driver = await uc.start()
tab = await driver.get("https://twitter.com")
# wait for text to appear instead of a static number of seconds to wait
# this does not always work as expected, due to speed.
print('finding the "create account" button')
create_account = await tab.find("create account", best_match=True)
print('"create account" => click')
await create_account.click()
print("finding the email input field")
email = await tab.select("input[type=email]")
# sometimes, email field is not shown, because phone is being asked instead
# when this occurs, find the small text which says "use email instead"
if not email:
use_mail_instead = await tab.find("use email instead")
# and click it
await use_mail_instead.click()
# now find the email field again
email = await tab.select("input[type=email]")
randstr = lambda k: "".join(random.choices(string.ascii_letters, k=k))
# send keys to email field
print('filling in the "email" input field')
await email.send_keys("".join([randstr(8), "@", randstr(8), ".com"]))
# find the name input field
print("finding the name input field")
name = await tab.select("input[type=text]")
# again, send random text
print('filling in the "name" input field')
await name.send_keys(randstr(8))
# since there are 3 select fields on the tab, we can use unpacking
# to assign each field
print('finding the "month" , "day" and "year" fields in 1 go')
sel_month, sel_day, sel_year = await tab.select_all("select")
# await sel_month.focus()
print('filling in the "month" input field')
await sel_month.send_keys(months[random.randint(0, 11)].title())
# await sel_day.focus()
# i don't want to bother with month-lengths and leap years
print('filling in the "day" input field')
await sel_day.send_keys(str(random.randint(0, 28)))
# await sel_year.focus()
# i don't want to bother with age restrictions
print('filling in the "year" input field')
await sel_year.send_keys(str(random.randint(1980, 2005)))
await tab
# let's handle the cookie nag as well
cookie_bar_accept = await tab.find("accept all", best_match=True)
if cookie_bar_accept:
await cookie_bar_accept.click()
await tab.sleep(1)
next_btn = await tab.find(text="next", best_match=True)
# for btn in reversed(next_btns):
await next_btn.mouse_click()
print("sleeping 2 seconds")
await tab.sleep(2) # visually see what part we're actually in
print('finding "next" button')
next_btn = await tab.find(text="next", best_match=True)
print('clicking "next" button')
await next_btn.mouse_click()
# just wait for some button, before we continue
await tab.select("[role=button]")
print('finding "sign up" button')
sign_up_btn = await tab.find("Sign up", best_match=True)
# we need the second one
print('clicking "sign up" button')
await sign_up_btn.click()
print('the rest of the "implementation" is out of scope')
# further implementation outside of scope
await tab.sleep(10)
driver.stop()
# verification code per mail
if __name__ == "__main__":
# since asyncio.run never worked (for me)
# i use
uc.loop().run_until_complete(main())