• 【爬虫】7.1. JavaScript动态渲染界面爬取-Selenium


    JavaScript动态渲染界面爬取-Selenium的简单学习

    引言:在学习这一章之前,若之前对于Ajax数据的分析和爬取有过了解的会知道,Ajax是JavaScript动态渲染界面的一种情形,通过直接分析Ajax,使我们仍然可以借助requests或urllib实现数据爬取。不过JavaScript动态渲染的界面不止Ajax一种,而且在实际中Ajax接口中会含有很多加密参数,比如说xhr中request url的链接中含有token参数使我们难以找到规律,所以很难直接通过分析Ajax爬取数据。

    ​ Python提供了许多模拟浏览器运行的库,例如Selenium、Splash、Pyppetter、Playwright等,可以帮我们实现所见皆所爬,就不用再为如何爬取动态渲染的界面发愁了。

    1. Selenium准备工作

    Selenium是一个自动化测试工具,利用它可以驱动浏览器完成特定的操作,例如点击、下拉等,你还可以利用这个工具来恶搞别人浏览不良网站!!!言归正传,你可以利用它获取浏览器当前呈现的页面源代码,做到所见皆所爬,对于一些JavaScript动态渲染的界面来说,这种爬取方式非常有效。 本节以Chrome浏览器为例来讲解Selenium。首先先安装好Chrome和Selenium库。

    首先来看看Selenium的功能:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.common.keys import Keys
    from selenium.webdriver.support import expected_conditions as EC
    from selenium.webdriver.support.wait import WebDriverWait
    import time
    
    browser = webdriver.Chrome()
    try:
        browser.get('https://www.baidu.com')
        input = browser.find_element(By.ID, 'kw')
        input.send_keys('Python')
        input.send_keys(Keys.ENTER)
        wait = WebDriverWait(browser, 10)
        wait.until(EC.presence_of_element_located((By.ID, 'content_left')))
        print(browser.current_url)
        print(browser.get_cookies())
        print(browser.page_source)
        time.sleep(1000)
    finally:
        browser.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    运行代码后,会自动弹出一个Chrome浏览器,接着浏览器会跳转到百度界面并在搜索框中输入Python,就会自动跳转到搜索结果页。输出页面源代码,此处省略源代码,可以知道,我们得到的当前URL、Cookie和页面源代码都是真实内容,而用requests请求的源代码却和使用Selenium的源代码不一样。所以说,用Selenium驱动浏览器加载的页面可以直接拿到JavaScript渲染的结果,无须关心是什么加密系统。

    2. Selenium简单用法

    2.1. 初始化浏览器对象-webdriver.Chrome()

    Selenium支持的浏览器非常多,既有Chrome、Firefox、Edge、Safari等电脑端的浏览器,也有Android、BlackBerry等手机端的浏览器。下面是初始化浏览器对象:

    from selenium import webdriver
    browser1 = webdriver.Chrome()
    # browser2 = webdriver.Firefox()
    # browser3 = webdriver.Edge()
    print(type(browser1))  # 
    browser1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.2. 访问界面-browser.get()

    上面我们已经初始化了浏览器对象,接下来就是要调用browser,执行各个方法模拟浏览器操作。访问界面:

    from selenium import webdriver
    url = "https://www.taobao.com/"
    browser1 = webdriver.Chrome()
    browser1.get(url)
    browser1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5

    2.3. 查找节点-find_element()

    我们想从淘宝页面提取搜索框这个节点,那就进入开发者选项快捷搜索就行了。

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    url = "https://www.taobao.com/"
    browser1 = webdriver.Chrome()
    browser1.get(url)
    input_first = browser1.find_element(By.ID, "q")
    print(input_first)
    # 
    browser1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    但是这种方法只会查找第一个匹配的节点,要是想要查找多个节点那就使用find_elements(),这时返回的内容是列表类型的,列表里面每一个元素都是WebElement类型的。

    2.4. 节点交互-send_keys()、clear()、click()

    Selenium可以驱动浏览器执行一些操作。比较常见的用法有:用send_keys方法输入文字,用clear方法清空文字,用click方法点击按钮:

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    import time
    url = "https://www.taobao.com/"
    browser1 = webdriver.Chrome()
    browser1.get(url)
    # 查找对话框,按钮
    input_first = browser1.find_element(By.ID, "q")
    button = browser1.find_element(By.CLASS_NAME, "btn-search")
    # 输入文字
    input_first.send_keys("IPad")
    time.sleep(3)
    # 清空文字
    input_first.clear()
    # 输入文字
    input_first.send_keys("HUAWEI")
    # 点击按钮
    button.click()
    time.sleep(3)
    browser1.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    更多操作请见官方文档

    https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.remote.webelement

    2.5. 动作链-ActionChains()

    在上面示例中,交互操作都是针对某一个节点来执行的。其实还有一些操作,他们没有特定的执行对象,比如鼠标拖拽、按键键盘等,这些操作需要另一种方式执行,那就是动作链。例如可以这样实现拖拽节点的操作:

    from selenium import webdriver
    from selenium.webdriver import ActionChains
    from selenium.webdriver.common.by import By
    import time
    
    browser = webdriver.Chrome()
    url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
    browser.get(url)
    # 切换到名为 'iframeResult' 的 iframe 内容(这个网页被分为很多个框架)
    browser.switch_to.frame('iframeResult')
    # 找到要拖拽的源元素和目标元素
    source = browser.find_element(By.CSS_SELECTOR, '#draggable')
    target = browser.find_element(By.CSS_SELECTOR,'#droppable')
    # 创建 ActionChains 对象来执行操作
    actions = ActionChains(browser)
    # 进行拖拽操作
    actions.drag_and_drop(source, target)
    # 执行
    actions.perform()
    time.sleep(5)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    更多的动作链请参考官方文档的介绍:

    https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.common.action_chains

    2.6. 运行JavaScript-execute_script()

    还有一些操作,Selenium没有提供API,例如下拉进度条,面对这种情况可以模拟运行JavaScript,此时使用execute_script方法可以实现,代码如下:

    from selenium import webdriver
    
    browser = webdriver.Chrome()
    browser.get('https://www.zhihu.com/explore')
    browser.execute_script('window.scrollTo(0, document.body.scrollHeight)')
    browser.execute_script('alert("To Bottom")')
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    2.7. 获取节点信息-get_attribute()、text、id、location、tag_name、size

    当我们选中一个节点时候,我们可以获取它的属性、文本、ID、位置、标签名和大小:

    • get_attribute():获取属性,参数传入属性名,额外:textContent子孙文本,innerHTML当前html代码,class获取元素的CSS类,id,src,class等
    • text:获取文本
    • id:获取id
    • location:获取位置
    • tag_name:获取标签名
    • size:获取大小

    2.8. 切换Frame-switch_to.frame()

    网页中有一种节点叫做iframe,也就是子Frame,相当于页面中的子页面,它的结构和外部网页的结构完全一致。Selenium打开一个页面之后,默认是在父Frame里操作,此时如果页面中含有子Frame,是不能在子Frame中操作的,这时就需要使用switch_to.frame方法切换Frame。

    需要注意的是,当你在子frame刷新出新的东西的时候,需要将frame切换回顶级frame,再切换回子frame

    import time
    from selenium import webdriver
    from selenium.common.exceptions import NoSuchElementException
    
    browser = webdriver.Chrome()
    url = 'http://www.runoob.com/try/try.php?filename=jqueryui-api-droppable'
    browser.get(url)
    browser.switch_to.frame('iframeResult')
    try:
        logo = browser.find_element_by_class_name('logo')
    except NoSuchElementException:
        print('NO LOGO')
    browser.switch_to.parent_frame()
    logo = browser.find_element_by_class_name('logo')
    print(logo)
    print(logo.text)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    2.9. 延时等待

    在Selenium中,get方法在网页框架结束后才会结束执行,如果我们尝试在get方法执行完毕时获取网页源代码,可能结果不是浏览器加载完全的页面,因为某些页面需要额外的Ajax请求还会经过JavaScript渲染,所以有必要让浏览器延时一段时间。这里的等待方式有两种,一种是隐式等待,一种是显式等待。

    2.9.1. 隐式等待-implicitly_wait()

    使用隐式等待执行测试时,在查找节点而节点没有立即出现时,隐式等待会先等待一段时间再查找DOM,默认的等待时间为0

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    
    browser = webdriver.Chrome()
    # 设置隐式等待时间
    browser.implicitly_wait(10)
    browser.get("https://spa2.scrape.center/")
    input_first = browser.find_element(By.CLASS_NAME, "logo-image")
    print(input_first.get_attribute("src"))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    2.9.2. 显式等待-WebDriverWait()

    隐式等待的效果其实并不好,因为我们只规定了一个固定时间,而页面加载时间会受网络条件的影响。还有一种更合适的等待方法——显式等待,这种方式会指定查找的节点和最长等待时间,如果在规定时间内加载出了要查找的节点,就返回这个节点;如果到规定时间内依然没有加载出节点,就抛出超时异常。

    from selenium import webdriver
    from selenium.webdriver.common.by import By
    from selenium.webdriver.support.ui import WebDriverWait
    from selenium.webdriver.support import expected_conditions as EC
    
    browser = webdriver.Chrome()
    browser.get("https://taobao.com")
    # 设定wait对象,指定最长等待时间
    wait = WebDriverWait(browser, 10)
    # 调用wait的until方法,传入等待条件
    input = wait.until(EC.presence_of_element_located((By.ID, "q")))
    # 等待条件:代表按钮式可点击的
    button = wait.until(EC.element_to_be_clickable((By.CSS_SELECTOR, ".btn-search")))
    print(input, button)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    下面表格列出了所有等待条件(应该)

    等待条件(Expected Conditions)描述
    EC.presence_of_element_located元素出现在 DOM 中。
    EC.visibility_of_element_located元素在页面上可见。
    EC.visibility_of元素在页面上可见。
    EC.invisibility_of_element_located元素在页面上不可见。
    EC.element_to_be_clickable元素可被点击。
    EC.element_to_be_selected元素被选中。
    EC.element_located_to_be_selected元素被选中。
    EC.text_to_be_present_in_element在元素中出现特定文本。
    EC.text_to_be_present_in_element_value在元素的值中出现特定文本。
    EC.frame_to_be_available_and_switch_to_it切换到指定的 iframe 或 frame 中。
    EC.element_to_be_located元素出现在 DOM 中。
    EC.staleness_of元素不再附加到 DOM 上(已过时)。
    EC.element_located_selection_state_to_be元素的选中状态与给定状态匹配。
    EC.element_selection_state_to_be元素的选中状态与给定状态匹配。
    EC.alert_is_present存在警报。
    EC.title_contains页面标题包含指定文本。
    EC.title_is页面标题等于指定文本。
    EC.url_contains页面 URL 包含指定文本。
    EC.url_to_be页面 URL 完全等于指定 URL。
    EC.url_matches页面 URL 匹配指定的正则表达式。
    EC.invisibility_of_element_located元素在页面上不可见。

    更多等待条件和参数及其用法可以参考官方文档:

    https://selenium-python.readthedocs.io/api.html#module-selenium.webdriver.support.expected_conditions

    2.10. 前进和后退-forward()、back()

    平时使用浏览器时也有前进和后退的功能,用Selenium也可以完成这个操作。

    from selenium import webdriver
    import time
    
    browser = webdriver.Chrome()
    browser.get("https://www.baidu.com/")
    browser.get("https://www.taobao.com/")
    browser.get("https://www.python.org")
    browser.back()
    time.sleep(3)
    browser.forward()
    time.sleep(3)
    browser.close()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    2.11. Cookie-add_cookie()、delete_all_cookies()

    使用selenium还可以对Cookie进行操作

    from selenium import webdriver
    
    browser = webdriver.Chrome()
    browser.get("https://www.zhihu.com/explore")
    print(browser.get_cookies())
    cookie = {'name': 'AJ', 'value': 'BJ'}
    browser.add_cookie(cookie)
    print(browser.get_cookies())
    browser.delete_all_cookies()  # 去掉all也可以指定删除哪一个
    print(browser.get_cookies())
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    2.12. 选项卡管理-execute_script()

    访问网页的时候,会开启一个个选项卡。在Selenium中,我们也可以对选项卡进行操作:

    import time
    from selenium import webdriver
    
    browser = webdriver.Chrome()
    browser.get("https://www.baidu.com/")
    # 打开一个新的选项卡
    browser.execute_script("window.open()")
    print(browser.window_handles)
    # 切换到新的选项卡
    browser.switch_to.window(browser.window_handles[1])
    browser.get("https://www.taobao.com")
    time.sleep(2)
    # 切换回去
    browser.switch_to.window(browser.window_handles[0])
    browser.get("https://python.org")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    2.13. 切换列表-Select()

    在访问网页的时候可能会发现一个列表,而它的网页源代码类似是这样的:

    <select name="week" id="week" onchange="se()">
    	<option value="">--请选择--option>
    	<option value="2023-09-03" selected="">第一周option>
    	<option value="2023-09-10">第二周option>
    	<option value="2023-09-17">第三周option>
    select>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这是一个html中的元素,通常用于创建下拉列表(也称为下拉框或者下拉菜单),而上面的οnchange="se()"是一个JavaScript事件处理程序,可以让用户在不同周时执行特定的JavaScript。下面是交互的代码:

    from selenium import webdriver
    from selenium.webdriver.support.ui import Select
    
    driver = webdriver.Chrome('chromedriver.exe')  # 请替换为您的WebDriver路径
    driver.get('URL_你的网页地址')
    
    # 选择下拉列表元素   --- 1
    week_dropdown = driver.find_element_by_id('week')
    
    # 创建一个Select对象,以便与下拉列表交互   --- 2
    week_select = Select(week_dropdown)
    
    # 通过文本值选择第二周   --- 3
    week_select.select_by_visible_text('第二周')
    # 通过值选择第三周   --- 3
    week_select.select_by_value('2023-09-17')
    
    # 获取当前选中的选项的文本和值
    selected_option = week_select.first_selected_option
    print('当前选中的文本:', selected_option.text)
    print('当前选中的值:', selected_option.get_attribute('value'))
    
    # 关闭浏览器
    driver.quit()
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    3. 反屏蔽

    现在有很多网站增加了对Selenium的检测,防止一些爬虫的恶意爬取,如果检测到有人使用Selenium打开浏览器就会直接屏蔽。大多数情况下,检测的基本原理时检测当前浏览器窗口下的window.navigator对象中是否包含webdriver属性。是因为正常使用浏览器时,这个属性应该是undefined,一旦使用了Selenium。它就会给window.navigator对象设置为webdriver属性。很多网站会通过JavaScript语句判断是否存在webdriver属性。

    在Selenium中,可以用CDP(即Chrome Devtools Protocol,Chrome 开发工具协议)解决这个问题,利用它可以实现在每个页面刚加载的时候就执行JavaScript语句,将webdriver属性置空。这里执行的CDP方法叫做Page.addScriptToEvaluateOnNewDocument,将上面的JavaScript语句传入其中即可。另外,还可以加入几个选项来隐藏WebDriver提示条和自动化扩展信息。

    from selenium import webdriver
    from selenium.webdriver import ChromeOptions
    import time
    
    option = ChromeOptions()
    option.add_experimental_option('excludeSwitches', ['enable-automation'])
    option.add_experimental_option('useAutomationExtension', False)
    browser = webdriver.Chrome(options=option)
    browser.execute_cdp_cmd('Page.addScriptToEvaluateOnNewDocument', {
        'source': 'Object.defineProperty(navigator, "webdriver", {get: () => undefined})'
    })
    browser.get('https://antispider1.scrape.center/')
    time.sleep(10)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    4. 无头模式

    上面的案例在运行的时候总会弹出一个浏览器窗口,我们也可以把它去掉,这样可以减少一些资源的加载:

    from selenium import webdriver
    from selenium.webdriver import ChromeOptions
    
    option = ChromeOptions()
    option.add_argument("--headless")
    browser = webdriver.Chrome(options= option)
    browser.set_window_size(1366, 768)
    browser.get("https://www.baidu.com")
    browser.get_screenshot_as_file("preview.png")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

  • 相关阅读:
    中国人民大学与加拿大女王大学金融硕士——人生总要逼自己一把
    解决表单action属性传参时值为null的问题
    文法与正规式
    向日葵远程分辨率过低解决办法
    数据管理知识体系指南(第二版)-第十章——参考数据和主数据-学习笔记
    SpringBoot整合easyexcel实现Excl导入导出
    嵌入式Linux driver开发实操(二十二):写一个ALSA驱动程序
    谷歌翻译器-在线实时批量谷歌翻译器
    正确理解党籍和党龄;入党和转正时间
    Django之VScode工程搭建
  • 原文地址:https://blog.csdn.net/likinguuu/article/details/132541648