菜鸡的selenium之旅

需求

(注:以下举例不包含任何恶意行为,如有不适请予以指正)

前几天帮朋友写一个爬虫,给出一些公司的code和公司高管姓名,要求爬取对应高管的一些公开信息,我们打开新浪财经网,先手动操作这个过程,首先是首页搜索框输入公司代码如000021

home_page

然后打开一个新的页面,这里我们点击进入公司页面

search_page

这里公司界面左下方我们找到公司高管的链接

com_page

之后进入公司高管页面

ceo_page

再之后就是搜索到对应的姓名点击链接进入并搜集需要的信息

初次实现

起初是打算用urllib之类的很底层的库来模拟get和post之类的过程而不打算使用浏览器,后来看网站的网络请求过程比较复杂于是就入坑了selenium

第一次使用selenium,由于是在家里只能百度搜索(吐槽下百度各种广告和搜不到)还有各种原因看不了官网文档,于是跟着网上的大部分博客写了个简单粗暴的版本

首先selenium的浏览器可以选择phantomjs,chrome,firefox,这里由于不想折腾太多环境问题,我就选择了chrome

环境配置

首先查看chrome版本,在浏览器标签栏输入chrome://version查看浏览器版本信息

chrome_version

然后对应如下表格找到对应chromeDriver版本下载安装

chromeDriver版本 chrome版本
v2.43 v69-71
v2.42 v68-70
v2.41 v67-69
v2.40 v66-68
v2.39 v66-68
v2.38 v65-67
v2.37 v64-66
v2.36 v63-65
v2.35 v62-64
v2.33 v60-62
v2.32 v59-61
v2.31 v58-60
v2.30 v58-60
v2.29 v56-58
v2.28 v55-57
v2.27 v54-56
v2.26 v53-55
v2.25 v53-55
v2.24 v52-54
v2.23 v51-53
v2.22 v49-52
v2.21 v46-50
v2.20 v43-48
v2.19 v43-47
v2.18 v43-46
v2.17 v42-43
v2.13 v42-45
v2.15 v40-43
v2.14 v39-42
v2.13 v38-41
v2.12 v36-40
v2.11 v36-40
v2.10 v33-36
v2.9 v31-34
v2.8 v30-33
v2.7 v30-33
v2.6 v29-32
v2.5 v29-32
v2.4 v29-32

这边是chromedriver的链接

接下来安装selenium(我的机子python2和3都装了,只装了3的可以只敲pip)

1
pip3 install selenium

初次搬砖

selenium的API参考官方说明

这边列举下常用接口(转自https://www.cnblogs.com/yufeihlf/p/5764807.html)

可用driver:

  • phantomjs
  • chrome
  • firefox

常用变量:

  • driver.current_url:用于获得当前页面的URL
  • driver.title:用于获取当前页面的标题
  • driver.page_source:用于获取页面html源代码
  • driver.current_window_handle:用于获取当前窗口句柄
  • driver.window_handles:用于获取所有窗口句柄

常用函数:

  • driver.find_element*():定位元素
  • driver.get(url):浏览器加载url。
    实例:driver.get(“http//:www.baidu.com")
  • driver.forward():浏览器向前(点击向前按钮)。
  • driver.back():浏览器向后(点击向后按钮)。
  • driver.refresh():浏览器刷新(点击刷新按钮)。
  • driver.close():关闭当前窗口,或最后打开的窗口。
  • driver.quit():关闭所有关联窗口,并且安全关闭session。
  • driver.maximize_window():最大化浏览器窗口。
  • driver.set_window_size(宽,高):设置浏览器窗口大小。
  • driver.get_window_size():获取当前窗口的长和宽。
  • driver.get_window_position():获取当前窗口坐标。
  • driver.get_screenshot_as_file(filename):截取当前窗口。
    实例:driver.get_screenshot_as_file(‘D:/selenium/image/baidu.jpg’)
  • driver.implicitly_wait(秒):隐式等待,通过一定的时长等待页面上某一元素加载完成。
    若提前定位到元素,则继续执行。若超过时间未加载出,则抛出NoSuchElementException异常。
    实例:driver.implicitly_wait(10) #等待10秒
  • driver.switch_to_frame(id或name属性值):切换到新表单(同一窗口)。若无id或属性值,可先通过xpath定位到iframe,再将值传给switch_to_frame()
  • driver.switch_to.parent_content():跳出当前一级表单。该方法默认对应于离它最近的switch_to.frame()方法。
  • driver.switch_to.default_content():跳回最外层的页面。
  • driver.switch_to_window(窗口句柄):切换到新窗口。
  • driver.switch_to.window(窗口句柄):切换到新窗口。
  • driver.switch_to_alert():警告框处理。处理JavaScript所生成的alert,confirm,prompt.
  • driver.switch_to.alert():警告框处理。
  • driver.execute_script(js):调用js。
  • driver.get_cookies():获取当前会话所有cookie信息。
  • driver.get_cookie(cookie_name):返回字典的key为“cookie_name”的cookie信息。
    实例:driver.get_cookie(“NET_SessionId”)
  • driver.add_cookie(cookie_dict):添加cookie。“cookie_dict”指字典对象,必须有name和value值。
  • driver.delete_cookie(name,optionsString):删除cookie信息。
  • driver.delete_all_cookies():删除所有cookie信息。

至于元素的搜索有tag_name,xpath,id等多种方式,这里不一一列举,详见文档

首先是浏览器的打开,这里用的是chrome引擎

1
2
3
browser = webdriver.Chrome()
url = 'https://finance.sina.com.cn/'
browser.get(url)

然后是关于超时加载的问题,这里我由于网速慢我设置了较长的等待时间等到一些元素加载完再执行js脚本让浏览器停止加载全部页面

1
2
3
4
5
6
7
8
def openHomePage(browser,url):
browser.set_page_load_timeout(240)
try:
browser.get(url)
except TimeoutException:
print('stop load')
browser.execute_script('window.stop ? window.stop() : document.execCommand("Stop");') #执行js脚本
return None

之后就是按要求搜索到对应html元素并且执行对应操作,,就是各种find_element然后click或者send_key,然后控制一下sleep来等待加载网页元素防止报错元素不存在即还未加载完这里就不一一贴代码了,后面会放代码链接

这里有一个用于切换tab页面的可以看一下

1
2
3
4
5
6
7
8
9
10
11
12
def switch_handle(handles,current_handle):
for handle in handles:
if handle != current_handle:
return handle

def switch_page(current_handle,handles,new_handle,browser):
current_handle = browser.current_window_handle
handles = browser.window_handles
new_handle = switch_handle(handles,current_handle)
browser.close()
browser.switch_to.window(new_handle)
return None

第一次搬砖过程中由于是第一次入坑selenium而且有点小偷懒,于是GUI也没关,就用上面那个步骤把输入文件读入后for了一下暴力解决,结果显而易见龟速爬虫,于是后面朋友还是类似操作的爬虫要求我进行了优化

进行优化

由于初次搬砖结果一般般,然后我开始考虑优化,首先一个是使用了GUI然后速度太慢,第二个由于等待加载过程什么也没做cpu空闲着可以考虑用多线程优化,第三个通过分析网页链接可以知道公司高管界面的url有一定规律,直接get这个界面省去前面的操作

关于无头浏览器,一开始我是去直接安装phantomjs但是遇到各种坑后面发现selenium后面不支持phantomjs了,有些接口是没维护的,于是后来使用了chrome的无头模式,也不会慢多少

无头模式可以在生成driver对象的时候填入chrome_options进行设置,下面代码顺便设置了一些service_args用来禁用一些图片加载和忽略一些网络错误以及启用浏览器cache

1
2
3
4
chrome_options = Options()
chrome_options.add_argument("--headless")
service_args = ['--ignore-ssl-errors=true','--ssl-protocol=TLSv1','--load-images=no','--disk-cache=yes']
browser = webdriver.Chrome(service_args=service_args,chrome_options=chrome_options)

关于多线程,这部分经历了一定的思考过程,由于不同线程之间的互斥访问资源,最后想到使用循环队列来解决这个问题,这里我写了一个类用来实现这部分功能

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
35
36
37
38
39
40
41
42
43
44
class conChrome:
chrome_max = 1 # 最大线程数
interval = 0.00001 # 两个线程之间创建时间间隔
timeout = 240 # 设置超时时间
chrome_options = Options()
service_args = ['--ignore-ssl-errors=true','--ssl-protocol=TLSv1','--load-images=no','--disk-cache=yes']

def __init__(self):
self.q_chrome = queue.Queue() #chromeDriver队列
self.chrome_options.add_argument("--headless")
self.q_output = queue.Queue() #偷懒用来存储输出
self.q_error = queue.Queue() #偷懒用来存储error

# 多线程创建driver
def open_chrome(self):
def open_threading():
browser = webdriver.Chrome(service_args=conChrome.service_args,chrome_options=conChrome.chrome_options)
browser.implicitly_wait(conChrome.timeout)
browser.set_page_load_timeout(conChrome.timeout)
self.q_chrome.put(browser) # 往队列末尾添加driver

th = []
for i in range(conChrome.chrome_max):
t = threading.Thread(target=open_threading)
th.append(t)
for i in th:
i.start()
time.sleep(conChrome.interval) #设置两个线程创建时间间隔
for i in th:
i.join()

# 多线程关闭chrome
def close_chrome(self):
th = []
def close_threading():
browser = self.q_chrome.get()
browser.quit()
for i in range(self.q_chrome.qsize()):
t = threading.Thread(target=close_threading)
th.append(t)
for i in th:
i.start()
for i in th:
i.join()

然后在类里面写一个getPage函数,get一个driver出队列,操作完put回去队列尾部,具体代码就不贴全部了给关键操作后面有链接

1
2
3
4
5
def getPage(url):
browser = self.q_chrome.get()
browser.get(url)
... # 一些爬虫操作抓取信息
self.q_chrome.put(browser)

最后是main里面创建多线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
cur = conChrome()
conChrome.chrome_max = 3
cur.open_chrome()
print ('chrome num is ',cur.q_chrome.qsize())
th = []
for i in input:
t = threading.Thread(target=cur.getPage,args=(i,))
th.append(t)
for i in th:
i.start()
for i in th:
i.join()

cur.close_chrome()

源码链接