自动化爬虫虽然方便,但希望大家能顾及网站服务器的承受能力,不要高频率访问网站。并且千万不要采集敏感数据!!否则很容易"从入门到入狱"
本系列大部分案例同时采用 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 最关键的功能就是"等待机制",我们可以用来检测各种条件,让代码无缝执行。