0%

爬虫系列(二)——操控浏览器模拟访问、点击与设置等待

关于如何实现爬虫,可以简单看这里:入门级别的难度,两个例子

更主要的目的是,把当时上课讲过的Selenium整理一下,毕竟当时我还做PPT上去讲课来,Selenium虽然是自动化测试工具,但也可以很好的用在爬虫领域。

在很多网页中使用了Ajax技术,即:你往下翻阅或者点击加载更多按钮才会加载剩下的内容,否则不会加载你暂时不看的内容。或者网页使用了反爬虫技术,爬取速度过快会严重影响对方服务器的流量,提高对方的成本,因此对方服务器会把你拉黑防止你的爬虫行为。

如何解决呢?

对于第一类问题,可以靠Selenium模拟网页浏览、按钮点击动作解决;
对于第二类问题,可以设置两种等待方式,放缓爬取速度,避免被拉黑。

所需软件和库:

  • selenium pip install 即可
  • chromeWebdriver 下载安装(firefox也有这个类似的)。我放到了D盘的根目录下,所以路径为’D:/chromedriver.exe’。

模拟按钮点击下一页

以著名的ICU996为例,我想看哪些人star了这个项目,且star这也页面的域名毫无规律可言,必须使用点击下一页的按钮才能看到下一页的结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import requests
from bs4 import BeautifulSoup
import pandas as pd
import numpy as np
import time
from selenium import webdriver

# 爬取网页的根域名
star_url = r"https://github.com/996icu/996.ICU/stargazers"

# 创建一个driver用于打开网页,记得找到brew安装的chromedriver的位置,在创建driver的时候指定这个位置
driver = webdriver.Chrome(r'D:/chromedriver.exe')
# 打开网页
driver.get(star_url)

name_counter = 1
page = 0;
while page < 500: # 爬500页的,这里是手工指定的 | 每页30个人 共249000人
print(page)
soup = BeautifulSoup(driver.page_source, "lxml")
data = p.findall(str(soup.body))
for i in data:
i = i.strip('Works for "')
if i in df.index:
po=df.loc[[i],:]
po.Sum += 1
else:
df.loc[i] = 1
# selenium的xpath用法,找到包含“下一页”的a标签去点击
driver.find_element_by_xpath("//a[contains(text(),'Next')]").click()
page = page + 1
# 睡0.5秒让网页加载完再去读它的html代码 防止爬虫过猛
time.sleep(0.5)
df.to_csv('9961.csv', sheet_name = 'watch')

这样一个简单的代码就完成了,但是爬下来的数据没有经过处理,如NCSTNCUST和华北理工大学表示的是一个东西,数据应该经过后期处理整合,但这里在说爬虫,到此为止。

在上面的例子中,已经介绍了一种等待方式:显示等待,time.sleep(0.5),而这也是显示等待中最糟糕的一个。即到这里强行停止0.5秒,然后在访问下一页,防止爬中速度快被拉黑。

模拟浏览器翻阅

主要是用代码控制浏览器,完成向下翻阅浏览的功能,两点好处:

  • 使服务器辨别不出来是人在看他的网页还是机器在看他的网页。
  • 触发Ajax,使浏览器自动加载剩下的内容,而不用使人去手动翻阅加载。如下图所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
from selenium import webdriver

driver = webdriver.Chrome(r'D:/chromedriver.exe')

def scroll_down(num):
for i in range (num):
# 向下翻阅1000个像素单位
driver.execute_script("window.scrollBy(0,1000)")
# 停两秒在向下翻阅 等他加载
time.sleep(2)

# 翻阅10次
scroll_down(10)

(0, 1000)中的0表示水平翻阅0个像素点,1000表示向下翻阅1000个像素点。还记得屏幕的分辨率是1920X1080吗,1080就是指竖直方向排列的像素点的数量。记住这段代码,一会儿配合其他任务一起用。

设置页面等待

隐性等待

官方文档永远是第一:
https://selenium-python-zh.readthedocs.io/en/latest/waits.html

如果某些元素不是立即可用的(可能页面不会第一时间加载该元素,可能浏览器翻阅到一定位置才加载),隐式等待是告诉WebDriver去等待一定的时间后去查找元素。 默认等待时间是0秒,一旦设置该值,隐式等待是设置该WebDriver的实例的生命周期(意思是:设置隐性等待30秒的话,全部代码全部的等待时间是30秒,第一次等了12秒,后续代码就只有剩下的18秒可用了)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from selenium import webdriver
import time

driver = webdriver.Chrome(r'D:/chromedriver.exe')
driver.implicitly_wait(10) # 隐性等待,最长等 10 秒

driver.get('https://cn.bing.com')

# 正常可以查找到
try:
driver.find_element_by_id('sb_form_q')
print('yes')
except:
print('false')

# 查找不到
since = time.time()
try:
driver.find_element_by_id('bs_form_q')
print('yes 1')
except:
print('false 1')
end = time.time()
finally:
driver.quit()

print(end - since)

输出如下:

1
2
3
yes
false 1
10.114457607269287

因为第一次能找到,所以输出yes,第二次找不到,等了10秒,输出了false。重点:一定要用异常捕获,否则寻找不到代码会强行停止,影响后续代码执行。

当然隐性等待只用于加载元素,而不是打开网页:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from selenium import webdriver
import time

driver = webdriver.Chrome(r'D:/chromedriver.exe')
driver.implicitly_wait(10) # 隐性等待,最长等 10 秒

page = [
"https://www.qq.com/",
"https://baidu.com",
"https://cn.bing.com",
"https://www.google.com/", # skip it automatic
"https://baidu.com"
]

for url in page:
since = time.time()
driver.get(url)
end = time.time()
print(end - since)

driver.quit()

输出如下(第四行输出显示:等了三十秒没打开网页 已经超出了设置的10秒,所以隐形等待是用来加载元素而不是加载整个网页):

1
2
3
4
5
3.0922935009002686
1.215383768081665
13.705082416534424
33.13365077972412 # 等了三十秒没打开网页 已经超出了设置的10秒
0.6389157772064209

实例

ECharts数据可视化实验室网站为例,(百度 ECharts 团队创建,联合公司内外众多数据可视化从业人组成的技术研究虚拟组织,致力于数据可视化的相关研究、教育普及、产品研发及生态建设。),虽然是百度的,但是这个可视化网站还不错。

而这个网站使用了Ajax技术,不往下翻阅是不会加载内容的,正好用上之前写的控制浏览器翻阅的代码。

我往下翻阅了很久,有个实例的作者叫 隐居威海 ,我们来爬取这个作者。(当然那个网站一直在变动,可能这个名字没了,或者加入了新的用例需要多翻阅几次,或者本次代码实效都很正常,主要介绍理念。我几个月前还是可以运行的。)

以下代码的效果:不往下翻阅浏览器加载不出来,只有向下加载才能定位到目标元素。意思是,不写scroll_down(10)会报错,写了这个函数才能爬取到。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
from selenium import webdriver
import time

driver = webdriver.Chrome(r'D:/chromedriver.exe')
# 隐性等待,最长等 30 秒
driver.implicitly_wait(30)

driver.get("https://gallery.echartsjs.com/explore.html#sort=rank~timeframe=all~author=all")

def scroll_down(num):
for i in range (num):
driver.execute_script("window.scrollBy(0,1000)")
time.sleep(2)

try:
since = time.time()
# 向下翻阅 10 次
scroll_down(10) # 这个代码注释掉程序会输出 loading......
# 因为下面有个作者是叫 隐居威海
print(driver.find_element_by_link_text('隐居威海').text)
end = time.time()
except:
end = time.time()
print('loading....')
finally:
print(end - since)
driver.quit()

scroll_down(10)的输出:

1
隐居威海

不写scroll_down(10)的输出

1
2
loading....
30.12252512365

显性等待

第一种等待方式time.sleep已经在模拟按钮点击下一页这一内容下介绍,并给出了程序实例,这也是最糟糕的。强制让浏览器等待X秒,不管当前操作是否完成,是否可以进行下一步操作,都必须等X秒的时间。

  • 缺点:不能准确把握需要等待的时间(有时操作还未完成,等待就结束了,导致报错;有时操作已经完成了,但等待时间还没有到,浪费时间),如果在用例中大量使用,会浪费不必要的等待时间,影响执行效率。

  • 优点:使用简单,可以在调试时使用。

另一种显示等待:程序每隔xx秒看一眼,如果条件成立了,则执行下一步,否则继续等待,直到超过设置的最长时间,然后抛出TimeoutException。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from selenium import webdriver
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time

driver = webdriver.Chrome(r'D:/chromedriver.exe')
driver.implicitly_wait(15)
# 隐性等待和显性等待可以同时用,但要注意:等待的最长时间取两者之中的大者
driver.get('https://liam.page/')
locator = (By.LINK_TEXT, 'NexT.Gemini')

since = time.time()
try:
WebDriverWait(driver, 10, 0.5).until(EC.presence_of_element_located(locator))
print('yes')
except:
print('no')
end = time.time()
finally:
end = time.time()
driver.close()

print(end - since)

输出:

1
2
yes
0.019946813583374023

可见用了不到一秒的时间加载了该元素。

结语

如果我们设置了隐性等待和显性等待,最长的等待时间取决于两者之间的大者,如果隐性等待时间 > 显性等待时间,则该句代码的最长等待时间等于隐性等待时间。

可以很好的利用自动化测试工具Selenium的元素查找、等待模式来完成爬虫中的工作。

等哪天心血来潮再去看看多线程异步爬虫吧。

感谢上学期间打赏我的朋友们。赛博乞讨:我,秦始皇,打钱。

欢迎订阅我的文章