之前在学校曾经用过request+xpath的方法做过一些爬虫脚本来玩,从ios正式转前端之后,出于兴趣,我对爬虫和反爬虫又做了一些了解,然后发现了selenium这个神器。selenium原本是一款测试工具,但由于他可以较好的模拟浏览器的各种操作,完全无视对于user-agent的限制,所以现在被更多的用于爬虫脚本中。这里记录一下借助selenium库进行爬虫时碰到的一些问题以及解决方法。(拒绝恶意爬虫从我做起)
基本操作
selenium的安装不多说, pip install selenium就行。不过要注意自己的python版本,要是3.x才行。用它打开浏览器,然后通过dom操作获取需要的dom节点。
from selenium import webdriver
import time
browser = webdriver.Chrome()
browser.get("url")
node = browser.find_elements_by_id("id")
nodes = browser.find_elements_by_css_selector("css-selector")
nodelist = browser.find_elements_by_class_name("class-name")
如果需要登录的,也可以事先将账号密码写好,然后用send_keys方法进行自动输入。
browser.find_element_by_xpath('xpath-to-dom').send_keys('account')
browser.find_element_by_xpath('xpath-to-dom').send_keys('password')
browser.find_element_by_xpath('xpath-to-dom').click()
然后需要什么就直接通过dom方法来获取。不过现在很多网站的url有防爬处理,使用了不规律的url,无法像豆瓣排行榜那样直接遍历。但这个不难,用selenium就是要模拟人的操作的,真人操作的时候也不会直接输url来一页一页看,比如在线阅读的网站,一般都会有个目录页。先爬取目录页面的信息,先将正文url列表保存下来,然后再遍历列表就行。
browser.get("url")
time.sleep(5)
nodes = browser.find_elements_by_css_selector("css-selector")
urlList = []
for i in range(0,len(nodes)):
url = nodes[i].get_attribute("href")
urlList.append(url)
这里有个sleep,目的是是确保目录页能完全加载完。当然这个方法有点蠢,后面我使用了不同的方法来做页面加载完成的判断。
等待加载完成
页面加载完成需要时间,一定要等页面加载好了才能获取页面元素。而直接设置一个固定的sleep显然是效率极低且容易出错的。这里有几种不同的方法来自动判断页面加载的情况。
1、分析页面元素
监视我最终需要的元素有没有加载完成,加载完成了就开始后续操作。比如我要的dom节点有一个类名为'page-content',并且在整个页面中一共有两处,而我需要的是第二处。那就可以监视这个节点的加载情况。
browser.get(url)
nodelist = browser.find_elements_by_class_name('page-content')
timeout = 0.0
while len(nodelist) < 2:
time.sleep(0.5)
nodelist = browser.find_elements_by_class_name('page-content')
timeout += 0.5
if timeout >= 10:
break
if timeout >= 10:
continue
node = browser.find_elements_by_class_name('page-content')[1]
这里设置了0.5秒的刷新周期,当然可以设置的更短,然后设置了10秒的timeout,超时自动打开下一章。
2、selenium隐性等待
browser.implicitly_wait(10)
browser.get(url)
这就很简单了,就一句话,最多等10秒,进行下一步。要是提前加载完就提前进行。这个方便是方便,但是不好用,他会等页面完全加载完才进行下一步,而事实上我只需要等正文加载完就行,所以效率上要差一点。
3、selenium显性等待
locator = (By.CLASS_NAME, 'page-content')
try:
WebDriverWait(driver, 10, 0.5).until(EC.presence_of_element_located(locator))
finally:
driver.close()
显性等待的好处就是可以在我需要的元素加载完的时候就进入下一步,获取元素内容,但是也有不好的地方,那就是还不够灵活。显性等待在超时的时候会抛出TimeoutException异常,在暴露的接口中没有给我定义异常处理的地方,这也是我选择自己实现一遍等待机制的原因,这样我可以对超时的异常进行处理。
人机验证
很多时候,我们会发现,在登录账号时,系统会要我们输入验证码。如果想要让脚本自动识别验证码,可能就涉及到图像识别技术,还有一些第三方的服务可以使用,也是可行的。
但有的时候,网站会使用更为复杂的人机验证。比如这样的:

这样的人机验证操作非常简单,只要一下点击,但实际上更为复杂。它会对于点击的操作进行判断,参考了鼠标移动的轨迹和速度等条件,来进行人机的判断。这样的话纯代码执行还是会有些困难。
同时这类智能人机验证还会有反爬虫识别,会对浏览器头进行检测,即使设置sleep然后手动点击进行人机验证的话也会失败。这时候如果打开浏览器dev tool,在控制台输入
window.navigator.webdriver,返回值会是“true”。


