0%

使用python和Selenium爬取网页

简介

最近使用八爪鱼爬虫软件在网上爬取了一部分数据,也想尝试一下实现一个简单的爬虫,这里是使用python的一次实践,我们要首先安装python和Selenium,Selenium就是一个软件可以驱动浏览器进行自动化测试的软件,安装方法很简单,可以自行百度.

爬虫原理

其实爬虫原理在我理解来看无非2种

  • 使用接口进行爬取:在我们日常开发网站中经常使用js和ajax请求来渲染页面,那如果我们能猜出请求的规律,那么我们可以直接发起http请求去请求这部分接口获取数据
  • 解析html页面:由于接口有可能加密,或者采用后端模板方式生成的html,那么我们这时只能通过解析html来爬取数据,又由于请求页面的时候,很多数据其实是页面加载完成后再去发起ajax请求获取数据再交由js去生成html,所以一般来说我们会使用Selenium或者PhantomJS来模拟浏览器来请求接口,等页面生成后再进行爬取

爬虫难点

其实爬虫难点并不是在于爬取数据,而是怎么绕过反爬机制,最常用的是验证码

  • 普通字符串验证码,通常验证码中除了有验证码外,还包含很多干扰信息来阻止机器识别验证码,验证码可以通过opencv处理完图像(如,转变成灰度图像,二值图像(需要设置好阈值),滤波),将验证码过滤掉多余的干扰信息后再交由ORC去识别图像,来提高验证码的识别率,常用的OCR类库为为tesseract
  • 拖拽式验证码,这部分可以使用Selenium来控制浏览器来拖拽滑动验证码,但是很多拖拽验证码都加入了机器识别功能,比如你使用机器匀速拖拽验证码时其会判定不通过
  • 点击试验证码,这部分是最难攻克的一部分,类似12306,很多验证码人眼都无法识别。。。这部分验证码的一个解决思路是爬取出所有验证码的图片,然后做归档,比如出现点击漏斗的图片,就去本地爬取漏斗的验证码中和网页上出现的漏斗验证码进行比对,比对一致点击(当然这种适用于验证码选择可能较少的情况),或者直接使用第三方服务
    服务端的反扒机制
  • IP限制,比如同一ip过多则不让操作
  • 代理限制:为了解决IP限制通常我们会使用代理,轮流切换IP进行爬取,但是服务端会监测你是否开启代理或者VPN,例如淘宝我在开启VPN时,淘宝会拒绝我的访问
  • 环境监测,有些服务器会监测本地环境,比如是否使用Selenium等
  • 登录监测: 站点要求用户登录,或者用户一次操作有次数限制,一般站点都会将用户一些登录凭据存在cookie中,因此我们可以使用本地cookie池来模拟多用户
    目前总体来说爬取APP的数据会比web容易,这就是为什么我之前看到的群控软件或者爬虫都会基于app端来做

实践

在本人第一次实践中,我试着爬取速卖通的商品数据(这个代码只展示了主要逻辑,还有很多异常重试等功能没有实现),至于为什么选择速卖通网站进行第一次练手,是因为比较好爬。。。。虽然同为阿里系,但是好像阿里并没有严格限制速卖通的爬取,如下代码并没有什么复杂逻辑,因为现在很多网站商品列表很长,它们可能会采用懒加载机制,在滚动条滚动到对应商品时才会进行加载,所以我们这里用selenium模拟了浏览器的滚动,让其加载数据,然后我们后续要做的就是选取要爬取的数据的元素节点(常用的选取是XPATH,或者id,class等,解析html的模块有很多,如lxml,Beautiful Soup或者是可以应用jquery语法来解析xml的pyquery),我这里使用的是selenium自带的解析html方法,在爬取完当前页面后,点击下一页,然后就是循环爬取页面了

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
45
46
47
48
49
50
51
52
53
54
55
56
import time

from selenium import webdriver
from selenium.common.exceptions import NoSuchElementException
from selenium.webdriver.common.keys import Keys

browser = webdriver.Chrome()
browser.get('https://www.aliexpress.com/premium/ipad.html')
size = browser.get_window_size();
height = size['height']

#滚动下拉框
def roll():
time.sleep(1)
for i in range(15):
script = "window.scrollBy(0," + str(height) + ")"
time.sleep(1)
browser.execute_script(script)
browser.execute_script("window.scrollBy(0," + str(-height) + ")")
time.sleep(1)

# 获取数据
def getData():
time.sleep(1)
lis = browser.find_elements_by_xpath("//ul[@class='list-items']/li")
for li in lis:
try:
a = li.find_element_by_xpath(".//a[@class='item-title']")
print(a.get_attribute('title'))
except NoSuchElementException:
continue
print('----------------------------')
time.sleep(1)

#翻页
def nextPage():
next = browser.find_element_by_css_selector(
"[class='next-btn next-medium next-btn-normal next-pagination-item next-next']")
# next.click() 出现异常无法点击元素
next.send_keys(Keys.ENTER)

#获取页数,做循环翻页使用
def getTotalPage():
totalPageSpan = browser.find_element_by_class_name("total-page").get_attribute('innerHTML')
pageTextArray = str(totalPageSpan).split(" ")
totalPageNum = pageTextArray[1]
return totalPageNum


roll()
getData()
pageNum = getTotalPage()
for i in range(int(pageNum)):
nextPage()
roll()
getData()

实践中遇到的问题

  • 页面懒加载问题:上文说过此问题,我们采用模拟浏览器滚动的方式来加载所有数据
  • 元素点击:在实践中我使用Selenium的click方法点击元素进行翻页时,会出现无法点击元素的异常(Message: element click intercepted),这是由于元素点击到了其它元素上去了,经过谷歌后发现可以使用send_keys(Keys.ENTER)方法来触发点击事件
  • 异常:真正使用代码爬取数据,需要处理好异常,因为Selenium获取不到页面元素就会抛出异常,而在爬取数据中由于网络等种种原因,爬取总是会失败的,要做好异常处理和重试机制,和后续数据去除。