一、爬虫环境搭建
爬虫目前的主流编写语言为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都已支持),甚至分布式