selenium_docker使用

Selenium Dokcer

我为了使用selenium,但是本地Chrome的版本太高了,Driver驱动不了。

启动命令

docker run -d -p 4444:4444 -p 7900:7900 --shm-size="2g" seleniarm/standalone-chromium:latest

4444端口是控制端口,python调用这个端口

7900是监控端口,通过vnc,没有特殊指示的话,密码是secret

python调用远程selenium

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
import undetected_chromedriver as uc
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

SELENIUM_REMOTE_URL = "http://localhost:4444/wd/hub"
options = uc.ChromeOptions()  # 使用uc来移除一些可能标识自己是爬虫的chrome配置
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_argument('--disable-infobars')  # 移除受控的条

driver = webdriver.Remote(
    command_executor=SELENIUM_REMOTE_URL,
    options=options
)

selenium 走代理

这个代码我是确定正常成功的,本质上创建了一个插件,然后通过插件来走的代理,如果不需要账号密码,就移除authCredentials这个函数即可

def create_proxyauth_extension(proxy_host, proxy_port, proxy_username, proxy_password, scheme='http', plugin_path=None):
    """代理认证插件

    args:
        proxy_host (str): 你的代理地址或者域名(str类型)
        proxy_port (int): 代理端口号(int类型)
        # 用户名密码认证(私密代理/独享代理)
        proxy_username (str):用户名(字符串)
        proxy_password (str): 密码 (字符串)
    kwargs:
        scheme (str): 代理方式 默认http
        plugin_path (str): 扩展的绝对路径

    return str -> plugin_path
    """

    if plugin_path is None:
        plugin_path = 'vimm_chrome_proxyauth_plugin.zip'

    manifest_json = """
    {
        "version": "1.0.0",
        "manifest_version": 2,
        "name": "Chrome Proxy",
        "permissions": [
            "proxy",
            "tabs",
            "unlimitedStorage",
            "storage",
            "<all_urls>",
            "webRequest",
            "webRequestBlocking"
        ],
        "background": {
            "scripts": ["background.js"]
        },
        "minimum_chrome_version":"22.0.0"
    }
    """

    background_js = string.Template(
        """
        var config = {
                mode: "fixed_servers",
                rules: {
                singleProxy: {
                    scheme: "${scheme}",
                    host: "${host}",
                    port: parseInt(${port})
                },
                bypassList: ["foobar.com"]
                }
            };

        chrome.proxy.settings.set({value: config, scope: "regular"}, function() {});

        function callbackFn(details) {
            return {
                authCredentials: {
                    username: "${username}",
                    password: "${password}"
                }
            };
        }

        chrome.webRequest.onAuthRequired.addListener(
                    callbackFn,
                    {urls: ["<all_urls>"]},
                    ['blocking']
        );
        """
    ).substitute(
        host=proxy_host,
        port=proxy_port,
        username=proxy_username,
        password=proxy_password,
        scheme=scheme,
    )
    with zipfile.ZipFile(plugin_path, 'w') as zp:
        zp.writestr("manifest.json", manifest_json)
        zp.writestr("background.js", background_js)
    return plugin_path

proxy_auth_plugin_path = create_proxyauth_extension(
    proxy_host="localhost",
    proxy_port=7890,
    proxy_username="123",
    proxy_password="123"
)

SELENIUM_REMOTE_URL = "http://192.168.123.239:4445/wd/hub"
options = uc.ChromeOptions()
options.add_experimental_option("excludeSwitches", ["enable-automation"])
options.add_argument('--disable-infobars')
options.add_extension(proxy_auth_plugin_path)
# options.add_argument('--proxy-server=socks5://localhost:7890') # 这个方式也可以,不过没法认证账号密码

driver = webdriver.Remote(
    command_executor=SELENIUM_REMOTE_URL,
    options=options
)

使用driver获取网页上的一些元素

driver.get("https://weread.qq.com/#search")  # 打开网页
search_button = driver.find_element(By.CLASS_NAME, "search_input_text")  # 找到有这个classname的元素,find_element会取第一个element,find_elements会返回一个列表,如果find_element没有找到会报错,find_elements不会,会返回空列表
search_button.send_keys("hello world")  # 将文本提交到上面的输入框当中
driver.execute_script('document.getElementsByClassName("search_input_right")[0].click()')  # 执行js脚本,这个是使用js模拟了点击,也可以使用driver.find_element(By.CLASS_NAME, "search_input_right").click()
last_height = driver.execute_script('return document.getElementsByClassName("search_result_global")[0].scrollHeight')  # 如果需要某个js执行的返回值,就加入return,数据类型也会对应的转换,数字给数字,字符串给字符串
wait = WebDriverWait(driver, 10)  # wait函数,会等待,知道返回了结果
ul_element = wait.until(EC.presence_of_element_located((By.CSS_SELECTOR, 'ul.search_result_global_list')))
li_elements = ul_element.find_elements(By.CSS_SELECTOR, 'li.search_result_global_item')
for index, li in enumerate(li_elements):
    # 根据页面结构获取标题、作者、简介、URL
    title = li.find_element(By.CSS_SELECTOR, 'p.search_result_global_bookTitle').text
    author = li.find_element(By.CSS_SELECTOR, 'p.search_result_global_bookAuthor').text
    description = li.find_element(By.CSS_SELECTOR, 'p.search_result_global_bookContent').text
    url = li.find_element(By.CSS_SELECTOR, 'a').get_attribute('href')  # 获取href属性
    print(title, author, url)

# 正如前文所说的,find_elements来判断是否存在某个属性是更加合适的。至于text_content的文本内容为什么要这么获取,因为selenium只能获取当前元素的文本,即使子元素有文本,可是就算我遍历了却还是获取不到,所以就改成了使用js代码来获取
title_list = []
for i, item2 in enumerate(li_elements):
    text_content = driver.execute_script(f'return document.getElementsByClassName("readerCatalog_list")[0].getElementsByTagName("li")[{i}].textContent')
    if item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_1"):
        title_list.append("level1: " + text_content)
    elif item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_2"):
        title_list.append("level2: " + text_content)
    elif item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_3"):
        title_list.append("level3: " + text_content)
    elif item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_4"):
        title_list.append("level4: " + text_content)
    elif item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_5"):
        title_list.append("level5: " + text_content)
    elif item2.find_elements(By.CSS_SELECTOR, ".readerCatalog_list_item_level_6"):
        title_list.append("level6: " + text_content)
    else:
        print("error", read_url)
html_source = driver.page_source  # 获取当前的html文本

如果要爬取很多内容,可以将爬取逻辑包裹在这个里面,核心逻辑是在try的最后面quit,报错了也quit。失败的内容就在跑完一批内容之后再重新跑

while 1:
  	if ...:
        break
    driver=...
    try:
        ...
        driver.quit()
    except Exception as e:
        driver.quit()

破解google recaptcha

使用一个插件即可。CAPTCHA Solver: auto hCAPTCHA reCAPTCHA freely 获取这个插件的crx文件,然后在启动的加载进去,这个插件会自动处理验证码。

如何获取这个crx呢?可以用这个插件Get CRX ,装了这个插件,再打开上面的链接,然后点击Get CRX插件就可以下载到上面的插件。

注意事项

使用远程的selenium,当需要更新driver的时候,一定要将上一个driver关掉,driver.quit() 不然无法开启下一次的网址打开。

Refrence

解决selenium爬虫加密代理问题(含socks5等一切代理)

selenium docker

selenium docker github

获取子节点文本,但是不知道为什么不生效,还是使用上面的js的方式获取吧

Subscribe to TaaLoo's Blog

Don’t miss out on the latest issues. Sign up now to get access to the library of members-only issues.
jamie@example.com
Subscribe