最近帮实验室收集整理数据,学习并使用了一下爬虫,本篇 结合实例 系统的整理一下,教你如何写出一个你所需要的爬虫。
《Python3.6+selenium+pytesser3 实现爬虫:含验证码和弹框的页面信息爬取》
一、什么是爬虫
网络爬虫 也叫 网络蜘蛛 ,即 Web Spider,名字非常形象。将某个网站比作一个蜘蛛网,那Spider就是在这张网上爬行的小蜘蛛。
我学习完后,喜欢将爬虫比喻为两种,一种是 巡逻型,一种是 捕猎型。
巡逻型:Spider 在蜘蛛网上巡逻,走遍蜘蛛网的每一根丝。 即从网站某一个页面开始,读取网页的内容,找到在网页中的其它链接地址,然后通过这些链接地址寻找下一个网页,一直循环下去,直到把整个网站所有的网页都抓取完为止。
捕猎型:Spider 在蜘蛛网上有目的的搜寻猎物。 即即从网站某一个页面开始,有目的的读取网页的内容,找到在网页中的自己所需要的相关匹配数据。
目前网络爬虫一般是用于 数据收集 ,在当今互联网大数据时代,如何能够搞到完整全面的数据,可是件极其重要且并不容易的事。要想做好数据分析,就需要从互联网上抓取真实、全面、有效的数据。
二、爬虫能干什么
爬虫能做很多很有趣的事,贴个某乎大家的回答:利用爬虫技术能做到哪些很酷很有趣很有用的事情?
贴部分回答大家感受下好了:
@冰蓝
之前在北京买房,谁想房价开始疯长,链家的房价等数据分析只给了一小部分,远远不能满足自己的需求。于是晚上花了几个小时的时间写了个爬虫,爬下了北京所有的小区信息及北京所有小区的所有历史成交记录。
@余生梦
说个简单实用的例子吧。昨晚突然发现我在某培训网站的的会员马上就要过期了,于是赶紧写了个爬虫,把没看完的教学视频全下载下来了……
@森羴
在用Python写网页爬虫之前,我只用来写过了一个驾校约车的脚本,让当时的我不惧上万的学车同僚,在约车环节没有输在起跑线上。
接着那段时间,我女朋友的领导每天下班都会下任务,要收集100条有招聘需求的信息,第二天检查。看到她熬夜百度+复制粘贴到半夜,心疼死了。
想到了某个牛人说:一切重复性的工作都可以用程序来完成。于是偷偷花了些时间研究了下她经常查的某些同类业务网站的页面数据,培育了这只爬虫。主要技能就是爬这些网站的招聘公司信息及联系方式,保存到Excel中。
在我将战斗成果----1000多个客户资料的Excel表格发给她的时候,先惊喜,后审问,再感慨!依稀记得那天她发了一条朋友圈,内容是:“有个程序员男朋友,感觉好幸福啊!!”成就感走直线啊,都能让她感到幸福,你说这只爬虫是不是做了很酷很有趣的事情呢?
三、爬虫环境搭建
爬虫目前的主流编写语言为python,确实非常好用,“人生苦短,我用python” 的宣传语也并非吹嘘。下面列出爬虫编写的环境需求和搭建方式:
python3.6:
python分为2.x和3.x两大版本,目前大部分的库已经迁移到了3.x,所以可以放心使用。版本选择的话觉得3.5/3.6都可以。可参照:python3.6——windows环境 来安装python。
selenium:
selenium 是一个web的自动化测试工具。简单的说就是一个可以用代码操所浏览器的工具,我我们可以通过selenium进行搜索关键字,点击按钮等等操作。该工具主要提供python和java库支持。我们选择python3.x版本的 selenium 进行安装即可。安装参照:Python3.x下Selenium3.x之安装篇
pytesser3:
pytesser3 是一个python的图像识别库,可用于简单的图像中的英文字母、符号、数字的识别,也可以用于中文等其他语言的识别,具体参照:wxPython利用pytesser模块实现图片文字识别。我们这里安装它主要用于网页中验证码的识别。
pytesser3的安装参照如下:
Python3 OCR技术(pytesser3)
造新轮子啦,让pytesser支持3.x啦~
pycharm:
PyCharm是一种Python IDE,带有一整套可以帮助用户在使用Python语言开发时提高其效率的工具,比如调试、语法高亮、Project管理、代码跳转、智能提示、自动完成、单元测试、版本控制。此外,该IDE提供了一些高级功能,以用于支持Django框架下的专业Web开发。我们也可以使用该IDE来写爬虫。
安装参照:Windows平台下PyCharm的安装
四、selenium方法介绍
这里简单下selenium的常用方法,以便于我们理解爬虫代码:
打开浏览器,加载页面:
from selenium import webdriver
driver = webdriver.Firefox() # Firefox浏览器
driver = webdriver.Chrome() # Chrome浏览器
driver.get(www.jianshu.com) # 打开
元素定位:
Selenium提供了8种定位方式。
id
name
class name
tag name
link text
partial link text
xpath
css selector
这8种定位方式在Python selenium中所对应的方法为:
find_element_by_id()
find_element_by_name()
find_element_by_class_name()
find_element_by_tag_name()
find_element_by_link_text()
find_element_by_partial_link_text()
find_element_by_xpath()
find_element_by_css_selector()
举例来说:
elem = driver.find_element_by_id('search') # 找到一个id为search的元素
xpath = ''
title = driver.find_element_by_xpath(xpath).text # 通过xpath找到一个元素的text
# 同样可以定位一组元素,注意是elements,结果为一个list
texts = driver.find_elements_by_xpath('//div/h3/a')
浏览器操作:
driver.back() # 后退
driver.forward() # 前进
driver.quit() #关闭所有窗口
driver.close() # 关闭单个窗口
driver.refresh() #刷新页面
WebDriver方法:
input = driver.find_element_by_id("kw")
input.clear() # 清除文本
input.send_keys("selenium") # 模拟按键输入,在输入框中输入值
driver.find_element_by_id("su").click() # 单击元素
attribute = driver.find_element_by_id("tag_a").get_attribute('href') # 获取标签属性
键盘操作:
# 删除多输入的一个 字符
driver.find_element_by_id("kw").send_keys(Keys.BACK_SPACE)
# ctrl+a 全选输入框内容
driver.find_element_by_id("kw").send_keys(Keys.CONTROL, 'a')
# 通过回车键来代替单击操作
driver.find_element_by_id("su").send_keys(Keys.ENTER)
设置元素等待:
WebDriverWait类是由WebDirver 提供的等待方法。在设置时间内,默认每隔一段时间检测一次当前页面元素是否存在,如果超过设置时间检测不到则抛出异常。
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.Firefox()
driver.get("http://www.baidu.com")
element = WebDriverWait(driver, 5, 0.5).until(EC.presence_of_element_located((By.ID, "kw")))
element.send_keys('selenium')
driver.quit()
表单切换:
比如126邮箱登录,想要操作登录框必须要先切换到登录框的iframe表单。
from selenium import webdriver
driver = webdriver.Chrome()
driver.get("http://www.126.com")
driver.switch_to.frame('x-URS-iframe') # 切换到登录表单
driver.find_element_by_name("email").clear()
driver.find_element_by_name("email").send_keys("username")
driver.find_element_by_name("password").clear()
driver.find_element_by_name("password").send_keys("password")
driver.find_element_by_id("dologin").click()
driver.switch_to.default_content() # switch_to.default_content()跳回最外层的页面
driver.quit()
窗口切换:
from selenium import webdriver
import time
driver = webdriver.Firefox()
driver.get("http://www.baidu.com")
# 获得当前窗口句柄
sreach_windows = driver.current_window_handle
driver.find_element_by_link_text('登录').click()
driver.find_element_by_link_text("立即注册").click()
# 获得当前所有打开的窗口的句柄
all_handles = driver.window_handles
# 进入注册窗口
for handle in all_handles:
if handle != sreach_windows:
driver.switch_to.window(handle) #用于切换到相应的窗口
print('now register window!')
driver.find_element_by_name("account").send_keys('username')
driver.find_element_by_name('password').send_keys('password')
time.sleep(2)
# ……
driver.quit()
警告框处理:
# switch_to_alert()方法获取当前页面上的警告框,accept()方法接受警告框
driver.switch_to.alert.accept()
当然,selenium还有文件上传、获取cookie、执行js等方法,如果需要使用可以自己搜索。
selenium参考链接:http://www.testclass.net/selenium_python/
五、知网爬虫实例
比如我们目前有一些学术期刊的标题信息,想从知网上爬取这些期刊对应的摘要、关键词、作者等信息用于分析,详注释爬虫源码如下:
import csv
import codecs
import os
import time
# 引入selenium库
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.common.exceptions import TimeoutException
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
# 获取原论文数据
path = 'C:\\base_data.csv'
csv_reader = csv.reader(open(path))
rows = []
# 去除表格里表头,start_index 表示除去表头,从第几行数据开始
start_index = 1
for row in csv_reader:
rows.append(row)
header = rows[0]
rows = rows[start_index:len(rows)]
driver = webdriver.Chrome()
# 打开知网搜索界面,此条用于测试浏览器驱动正常
driver.get('http://kns.cnki.net/kns/brief/default_result.aspx')
# 打开首页,当页面出现等待延迟不加载的时候,等待10s,重新加载
def open_base_page():
try:
driver.get('http://kns.cnki.net/kns/brief/default_result.aspx')
time.sleep(3)
WebDriverWait(driver, 10).until(EC.presence_of_element_located((By.ID, 'iframeResult')), 'timeout')
except TimeoutException:
time.sleep(10)
open_base_page()
# 设定好所需数据路径,这里拿摘要举例
abstracts_path = 'C:\\abstracts\\'
if not os.path.exists(abstracts_path):
os.makedirs(abstracts_path)
# result.txt 记录爬虫情况:no hit表示未找到,hit表示搜索到,
# no Abstract表示论文页面没有该信息
result_file = codecs.open('C:\\result.txt', 'w', 'utf-8')
for i in range(0, len(rows)):
# i 为rows下标序号,num为当前起始序号,代码如果半途中断,修改start_index即可继续爬取剩下数据
num = i + start_index
try:
# 打开搜索页
open_base_page()
# 输入期刊标题
elem = driver.find_element_by_id("txt_1_value1")
elem.send_keys(rows[i][1])
# 设置等待延时,使爬虫更加拟人,防止反爬虫技术干扰
time.sleep(10)
# 进行搜索
elem.send_keys(Keys.RETURN)
flag = False
# 文件名称:序号_类型
filename = str(num) + '_abstract'
# 用于存储爬取的摘要
ab_info = codecs.open(abstracts_path + filename, 'w', 'utf-8') # 论文摘要
# 切换到搜索结果的iframe,id为iframeResult
driver.switch_to.frame('iframeResult')
# 设置跳转延时5秒,太快可能无法加载出来
time.sleep(5)
paper_results = driver.find_elements_by_xpath("//table[@class='GridTableContent']/tbody/tr")
# temp参数用于搜索结果计数,temp为1时,为表头
temp = 1
find_flag = False # 记录是否找到
paper_link = '' # 记录匹配结果链接
# 检验搜索结果
for paper_detail in paper_results:
if temp == 1:
temp = temp + 1
continue
else:
# 先获取元素的xpath路径,再查取值
base_xpath = '// *[ @ id = "ctl00"] / table / tbody / tr[2] / td / table / tbody /'
xpath = base_xpath + 'tr[' + str(temp) + ']'
temp = temp + 1
paper_title = paper_detail.find_element_by_xpath(xpath + "/td[2]/a").text
paper_li = paper_detail.find_element_by_xpath(xpath + "/td[2]/a").get_attribute('href')
# 进行匹配:
# 拿专利名匹配,如果完全匹配,则break
if paper_title == rows[i][1]:
paper_link = paper_li
find_flag = True
break
if flag:
# 打开匹配结果的链接
driver.get(paper_link)
time.sleep(5)
# 获取摘要
abstract = driver.find_element_by_id('ChDivSummary').text
ab_info.write(abstract + '\n')
# 打印爬取结果
print(str(num) + ' hit')
result_file.write(str(num) + ' hit' + '\n')
else:
print(str(num) + ' no hit')
result_file.write(str(num) + ' no hit' + '\n')
ab_info.close()
except Exception as err:
# 将所有异常项先统一打印,最后再分析整理,从而不影响爬虫爬取
print(err)
print(str(num) + ' Exception!')
result_file.write(str(num) + ' Exception! ' + str(err) + '\n')
continue
result_file.close()
driver.quit()
六、处理爬虫页面中的验证码
知网网站是没有验证码的,有些数据网站页面中,爬取时含有验证码,如图:
这种时候就要使用pytesser3了。轻松实现验证码识别。处理方式如下:
# 循环输入验证码,因为一遍可能不能正确识别,直到正确识别,再进行其他操作
accept = False
while not accept:
try:
# 打开含有验证码的搜索页
driver.get('your link')
time.sleep(3)
# 找到输入框input,输入你的搜索关键词
input = driver.find_element_by_id('keyword')
input.send_keys('your keyword')
time.sleep(5)
# 验证码识别
# 先对浏览器当前页面截图,并存储
driver.get_screenshot_as_file('C:\\screenshot.jpg')
im = Image.open('C:\\screenshot.jpg')
# 用box裁剪出截图中验证码的所在区域
box = [100, 100, 200, 200] # 设置要裁剪的区域
region = im.crop(box) # 此时,region是一个新的图像对象
region.save('C:\\codeImage.jpg')
time.sleep(3) # 防止由于网速,可能图片还没保存好,就开始识别
im = Image.open('C:\\codeImage.jpg')
imgry = im.convert('L') # 图像加强,二值化
sharpness = ImageEnhance.Contrast(imgry) # 对比度增强
sharp_img = sharpness.enhance(2.0)
# 将处理后的验证码图片存在code.jpg中
sharp_img.save('C:\\code.jpg')
# sharp_img.show() #这是分布测试时候用的,整个程序使用需要注释掉
# 调用pytesser3方法,变量code即为识别出的图片数字str类型
code = pytesser3.image_file_to_string('C:\\code.jpg', graceful_errors=True)
print('code:' + code)
# 在页面的验证码输入框中输入识别出的code
code_input = driver.find_element_by_id('keyword2')
code_input.send_keys(code)
time.sleep(2)
# 然后进行搜索和后续操作
driver.find_element_by_class_name('search').click()
time.sleep(2)
# 如果验证码没有识别正确,可能会弹出提示框,这里我们需要对提示框进行处理
# 在页面中寻找提示框
res = EC.alert_is_present()(driver)
# 如果弹出提示框
if res:
# 点击提示框的确认,从新搜索一遍
res.accept()
time.sleep(5)
else:
# 说明已经识别成功并搜索成功,跳出循环进行下一步操作
accept = True
except UnicodeDecodeError:
accept = False
time.sleep(3)
七、反爬虫技术
robots.txt
大多数网站都会定义robots.txt文件,让爬虫了解爬取网站的限制。robots.txt虽然是作为建议给出,但作为良好的网络公民都应遵守这些限制。robots.txt放置在一个站点的根目录下,而且文件名必须全部小写。例如:
https://jingyan.baidu.com/robots.txt。一般robots.txt中会规定什么能爬什么不可以,以及每分钟访问的次数限制等等。如果网站规定了robots.txt,并且我们遵守其规则编写爬虫,爬虫是不会被限制的,可以正常获取我们所需要的数据。
反爬虫介绍
反爬虫的技术介绍很多,我贴三篇写的很好的博客和回答,有兴趣的可以阅读。
关于反爬虫,看这一篇就够了
Web Crawler with Python - 07.反爬机制
如何应对网站反爬虫策略?如何高效地爬大量数据?
我们写爬虫时需要注意什么
作为一个良好的网络公民,我不想在博客中介绍反反爬虫技术的相关内容,爬虫和反爬虫从技术上说是程序员的博弈。其交锋只会在发生利益碰撞时产生。所有我们搜取数据时仅 合理、适当 的搜取我们所需要的数据,就不会被反爬虫技术所困扰。
Tips:
1.注意设置访问频率,多添加等待,降低网站访问压力。
2.尽量减少请求次数,能抓列表页就不抓详情页。
3.如果真的对性能要求很高,可以考虑多线程(一些成熟的框架如 scrapy都已支持),甚至分布式。