Python无头爬虫Selenium系列(02):等待机制

2021-09-01 14:45:27 浏览数 (1)

自动化爬虫虽然方便,但希望大家能顾及网站服务器的承受能力,不要高频率访问网站。并且千万不要采集敏感数据!!否则很容易"从入门到入狱"

本系列大部分案例同时采用 selenium 与 pyppeteer 库讲解,并且有 Python 和 C# 2门语言的实现文章,详细请到公众号目录中找到。

前言

使用 Selenium 控制浏览器进行页面跳转时,经常需要等待机制才能让爬虫继续执行,这次我们来看看等待机制的流程,如何随心所欲做出各种等待效果。


机制

想象一下如果是一个机器人帮你从网页上查找某个信息,比较合理的流程是:

  • 让机器人每隔1秒到页面上"按规则"找一下
  • 如果找到,则通知你
  • 如果找不到,下一秒继续
  • 如果超过10秒都找不到,通知你

Selenium 的等待机制同样如此,而上述机制中唯一可以变化的就是"查找规则",这体现为 wait.until 的第一个参数接受一个"可调用对象"


终于得到你

这次案例的网页是我简单创建的,启动网站服务如下(jupyter notebook 为例子):

  • 打开 web_run.ipynb 文件
  • 执行第一个 cell 的代码,直到下方出现"serving at port 8081"
  • 打开浏览器页,输入 "localhost:8081/web_sp" 出现页面
  • 点击页面上的按钮,下方出现新文本

现在用代码对这个页面采集,看看网页内容结构。

用"开发者工具",查看元素的标签:

  • 每个新增的内容为一个 div 标签,属性 class 都是 "content"

现在用代码控制 Selenium ,找上述的 div 标签。

首先导入包:

代码语言:javascript复制
from selenium import webdriver
import selenium.webdriver.support.wait as WA

主要代码如下:

代码语言:javascript复制
driver = webdriver.Chrome()
driver.get('http://localhost:8081/web_sp/')

wait = WA.WebDriverWait(driver, poll_frequency=0.5, timeout=10)
ct = wait.until(lambda w: w.find_element_by_css_selector('div.content'))
ct.text
  • 行4:定义 WebDriverWait
  • 第一个参数传入 driver
  • 参数 poll_frequency=0.5 是每 0.5 秒执行一次查找
  • 参数 timeout=10 是 10 秒都没有找到任何东西,就超时错误
  • 行5:调用 wait.until 方法,参数就一个,传入一个"可调用对象"(此处是一个 lambda),wait 对象会每隔 0.5 秒执行一次这个方法
  • css 选择器 "div.content" 相当于 "div[class=content]"
  • 行6:打印一下找到的文本

现在执行这个代码,如下:

  • 一开始,你会发现代码被卡住,其实是卡在行5的代码上
  • 因为此时浏览器上一直没有找到 class 属性为 "content" 的标签

大概 10 秒后,代码执行结束,报了一个错误:

  • 行5 中,wait.until 中的 lambda,大概被执行了 20 次(0.5秒一次,执行了10秒)

我们再次执行代码,这次我们在页面出来之后10秒内,点击页面上的按钮:

  • 这次代码执行完毕,并执行到行6,得到我们要的结果

等你 n 次

了解这个机制,我们可以很灵活定制属于自己的查找条件。

这次,我希望可以等新增内容到达一定次数才继续执行后续的操作。

首先,我们要知道一点,传入 wait.until 的方法是有限制的,必须只有一个参数(此参数实际为 driver)。

但是,我们希望自定义函数能够灵活一点,可以知道 css 选择器 和 条件数量,比如:

代码语言:javascript复制
def finds_by_count(css_selector, num):
    pass

我们可以利用嵌套函数实现:

代码语言:javascript复制
def finds_by_count(css_selector, num):
    def finds_by_count_(wd):
        res = wd.find_elements_by_css_selector(css_selector)
        if len(res) >= num:
            return res

    return finds_by_count_
  • 行2到5:符合 wait.until 参数的函数(只有一个参数)
  • 行3:使用 find_elements_by_css_selector 查找元素,此时我们可以在"下级函数"中使用"上级函数"的参数 css_selector 。此方法不管是否找到元素,都会返回一个列表(没有找到则为空列表)
  • 行4,5:一旦找到的数量高于等于指定数量,则把找到的列表返回即可。如果没有找到,没有执行 return ,相当于返回 none
  • 行7:最关键的代码,这是"上级方法" finds_by_count 的返回语句,把"下级方法" finds_by_count_ 返回出去。注意只是返回 finds_by_count_ ,而没有调用他(因为方法名字后面没有括号)

现在,试试效果了,代码如下:

代码语言:javascript复制
driver = webdriver.Chrome()
driver.get('http://localhost:8081/web_sp/')

wait = WA.WebDriverWait(driver, timeout=10)
cts = wait.until(finds_by_count('div.content', 3))
[c.text for c in cts]
  • 行5:wait.until 中调用"上级方法" finds_by_count ,等待 class 属性为 content 的 div 标签,出现3个为止

出来页面后,如果快速点击3下按钮,就能看到结果:

  • 如果10秒内没有出现3个文本,就会超时错误

总结

用代码控制 selenium 最关键的功能就是"等待机制",我们可以用来检测各种条件,让代码无缝执行。

0 人点赞