Spider

[TOC]

1.基本概念

爬虫就是获取网页并提取和保存信息的自动化程序

1.HTTP基本原理理

1.URI和URL

URI (Uniform Resource Identifier) 统一资源标志符
URL(Universal Resource Locator) 统一资源定位符

URL是URI的子集, URI还包括一个子类URN (Universal Resource Name) 统一资源名称,URN 只命名资源不指定如何定位资源。

2.超文本 (hypertext)

网页源代码,html代码 查看源代码工具和方法

3.HTTP和HTTPS

HTTP (Hyper Text Transfer Protocol) 超文本传输协议
HTTPS (Hyper Text Transfer Protocol over Secure Socket Layer)

HTTP加入SSL层,传输内容通过SSL加密
安全通道保证数据传输安全
确认网站真实性

4.HTTP请求过程

用浏览器开发者工具观察网络请求过程

1.请求

请求方法 (Request Method)
GET请求的参数直接在URL里,最多只有1024字节
POST请求数据一般通过表单提交,不会出现在URL里,大小没有限制

序号 方法 描述
1 GET 请求指定的页面信息,并返回实体主
2 POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件),数据被包含在请求体中。POST请求可能会导致新的资源的建立或已有资源的修改
3 HEAD 类似GET请求,只不过返回的响应中没有具体内容,用于获取报头
4 PUT 从客户端向服务器传送数据取代指定的文档的内容
5 DELETE 请求服务器删除指定的页面
6 CONNECT http/1.1协议中预留给能够讲连接改为管道方式的代理服务器
7 OPTIONS 允许客户端查看服务器的性能
8 TRACE 回显服务器收到的请求,主要用于测试或诊断

2.请求头

Cache-Control

指定了服务器和客户端在交互时遵循的缓存机制,即是否要留下缓存页面数据。 一般在使用浏览器访问时,都会在计算机本地留下缓存页面,相当于是浏览器中的页面保存和 下载选项。但是爬虫就是为了从网络上爬取数据,所以几乎不会从缓存中读取数据。所以在设置的时候要侧重从服务器请求数据而非加载缓存。

  • no-cache:客户端告诉服务器,自己不要读取缓存,要向服务器发起请求
  • no-store:同时也是响应头的参数,请求和响应都禁止缓存,即不存储
  • max-age=0:表示当访问过此网页后的多少秒内再次访问,只加载缓存,而不去服务器请求,在爬虫时一般就写0秒 .
  • 一般爬虫就使用以上几个参数,其他的参数都是接受缓存的,所以就不列出了。
User-Agent

中文名:用户代理,服务器从此处知道客户端的操作系统类型和版本,电脑CPU类型,浏览器种类版本,浏览器渲染引擎,等等。这是爬虫当中最重要的一个请求头参数,所以一定要伪造,甚至多个。如果不进行伪造,而直接使用各种爬虫框架中自定义的user-agent,很容易被封禁。
举例: User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:52.0) Gecko/20100101 Firefox/52.0 User-Agent: Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36

Accept

指定客户端可以接受的内容类型,比如文本,图片,应用等等,内容的先后排序表示客户端接收的先后次序,每种类型之间用逗号隔开。 其中,对于每一种内容类型,分号 ; 后面会加一个 q=0.6 这样的 q 值,表示该种类型被客户端喜欢接受的程度,如果没有表示 q=1,数值越高,客户端越喜欢这种类型。 爬虫的时候,一般会伪造若干,将想要找的文字,图片放在前面,其他的放在后面,最后一定加上/;q=0.8。
比如Accept: imagegif,imagex-xbitmap,imagejpeg,applicationx-shockwaveflash,applicationvnd.ms-excel,applicationvnd.ms-powerpoint,applicationmsword,

  • textxml,textshtml:文本类型,斜杠后表示文档的类型,xml,或者shtml
  • applicationxml,applicationxhtml+xml:应⽤用类型,后⾯面表示⽂文档类型,比如 flash动画,excel 表格等等
  • imagegif,imagex-xbitmap:图片类型,表示接收何种类型的图片
  • /:表示接收任何类型,但是这一条一般写在最后,表示优先接收前面规定的类型,然后再加载其他类型。
Accept-Language

客户端可以接受的语言类型,参数值规范和 accept的很像。一般就接收中文和英文,有其他语言需求自行添加。
比如: Accept-Language: zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4

  • zh-CN:中文简体大陆?
  • zh:其他中⽂文
  • en-US:英语美语
  • en:其他英语
Accept-Encoding

客户端接收编码类型,一些网络压缩格式:
Accept-Encoding: gzip, deflate, sdch。相对来说,deflate是一种过时的压缩格式,现在常用的是gzip

Accept-Charset

指的是规定好服务器处理表单数据所接受的字符集,也就是说,客户端浏览器告诉服务器自己的表单数据的字符集类型,用以正确接收。若没有定义,则默认值为“unknown”。如果服务器没有包含此种字符集,就无法正确接收。一般情况下,在爬虫时不定义该属性,如果定义,例子如下:
Accept-Charset:gb2312,gbk;q=0.7,utf-8;q=0.7,*;q=0.7

Referer

浏览器上次访问的网页url,uri。由于http协议的无记忆性,服务器可从这里了解到客户端访问的前后路径,并做一些判断,如果后一次访问的 url 不能从前一次访问的页面上跳转获得, 在一定程度上说明了请求头有可能伪造。

DNT

是 do not track 的缩写,告诉服务器,浏览器客户端是否禁止第三方网站追踪。这一条主要是用来保护浏览器用户隐私的,通过此功能,用户可以检测到跨站跟踪、cookie跟踪等等。 在爬虫时一般都是禁止的。数字1代表禁止追踪,0代表接收追踪,null代表空置,没有规定。

Connection

请求头的 header字段指的是当 client 浏览器和 server 通信时对于长链接如何处理。由于http 请求是无记忆性的,长连接指的是在 client 和server 之间建立一个通道,方便两者之间进行多次数据传输,而不用来回传输数据。有 close,keep-alive 等几种赋值,close表示不想建立长连接在操作完成后关闭链接,而keep-alive 表示希望保持畅通来回传输数据。 爬虫时一般都建立一个长链接。

Proxy-Connection

当使用代理服务器的时候,这个就指明了代理服务器是否使用长链接。但是,数据在从client 到代理服务器,和从代理服务器到被请求的服务器之间如果存在信息差异的话,会造成信息请求不到,但是在大多数情况下,都还是能够成立的。

Pragma

防止页面被缓存, 和 cache-control类似的一个字段,一般爬虫都写成 no-cache。

Cookie

同样是一个比较关键的字段,Cookie是 client 请求服务器时,服务器会返回一个键值对样的数据给浏览器,下一次浏览器再访问这个域名下的网页时,就需要携带这些键值对数据在 Cookie中,用来跟踪浏览器用户的访问前后路径。 在爬虫时,根据前次访问得到 cookie数据,然后添加到下一次的访问请求头中。

Host

访问的服务器主机名,比如百度的 www.baidu.com。这个值在爬虫时可以从访问的 URI 中获得。

If-Modified-Since

只有当所请求的内容在指定的日期之后又经过修改才返回它,否则返回304。其目的是为了提高访问效率。但是在爬虫时,不设置这个值,而在增量爬取时才设置一个这样的值,用以更新信息。

Authorization

当客户端接收到来自WEB服务器的 WWW-Authenticate 响应时,该头部来回应自己的身份验证信息给WEB服务器。主要是授权验证,确定符合服务器的要求。这个在爬虫时按需而定。

一个典型的适用于爬虫爬取数据的伪造请求头如下所示:
“Proxy-Connection”: “keep-alive”,
“Pragma”: “no-cache”,
“Cache-Control”: “no-cache”,
“User-Agent”: “Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36”,
“Accept”:“texthtml,applicationxhtml+xml,applicationxml;q=0.9,imagewebp,/;q=0.8”, “DNT”: “1”,
“Accept-Encoding”: “gzip, deflate, sdch”,
“Accept-Language”: “zh-CN,zh;q=0.8,en-US;q=0.6,en;q=0.4”,
“Referer”: “https://www.baidu.com/s?wd=%BC%96%E7%A0%81&rsv_spt=1&rsv_iqid=0x9fcbc99a0000b5d7&issp=1&f=8&rsv_ bp=1&rsv_idx=2&ie=utf-8&rqlang=cn&tn=baiduhome_pg&rsv_enter=0&oq=If-NoneMatch&inputT=7282&rsv_t=3001MlX2aUzape9perXDW%2FezcxiDTWU4Bt%2FciwbikdOL
QHYY98rhPyD2LDNevDKyLLg2&rsv_pq=c4163a510000b68a&rsv_sug3=24&rsv_sug1=14 &rsv_sug7=100&rsv_sug2=0&rsv_sug4=7283”, “Accept-Charset”: “gb2312,gbk;q=0.7,utf-8;q=0.7,*;q=0.7”,

请求体 (Request Body)

POST请求体有内容,GET请求,请求体为空
设置Request Header Content-Type

  • application/x-www-form-urlencoded 表单数据
  • multipart/form-data 表单⽂文件上传
  • application/json 序列列化json数据
  • text/xml xml数据

响应

响应状态码 (Response Status Code)

image.png

响应头,其中包含了服务器对请求的应答信息,如 Content-Type、Server、Set-Cookie 等, 下面将一些常用的头信息说明如下:

  • Date,标识 Response 产生的时间。
  • Last-Modified,指定资源的最后修改时间。
  • Content-Encoding,指定 Response 内容的编码。
  • Server,包含了了服务器的信息,名称,版本号等。
  • Content-Type,文档类型,指定了返回的数据类型是什什么,如texthtml 则代表返回 HTML 文 档,applicationx-javascript 则代表返回 JavaScript 文件,image/jpeg 则代表返回了了图⽚片。
  • Set-Cookie,设置Cookie,Response Headers 中的 Set-Cookie即告诉浏览器需要将此内容放在 Cookies 中,下次请求携带 Cookies 请求。
  • Expires,指定 Response 的过期时间,使用它可以控制代理服务器或浏览器将内容更新到缓存中,如果再次访问时,直接从缓存中加载,降低服务器负载,缩短加载时间。

响应体 Resposne Body

即响应体,最重要的当属响应体内容了,响应的正文数据都是在响应体中,如请求一个网页, 它的响应体就是网页的HTML 代码,请求一张图片,它的响应体就是图片的二进制数据。所以最主要的数据都包含在响应体中了了,我们做爬虫请求网页后要解析的内容就是解析响应体。

网页的组成

html, css, javascript

网页结构和节点关系

目标 CSS XPath
所有元素 * //*
所有的P元素 p //p
所有的P元素的子元素 p>* //p/*
根据ID获取元素 #foo //*[@id='foo']
根据Class获取元素 .foo //*[contains(@class,'foo')]
拥有某个属性的元素 *[title] //*[@title]
所有P元素的第一个子元素 p>*:first-child //p/*[0]
所有拥有子元素a的P元素 无法实现 //p[a]
下一个兄弟元素 p+* //p/following-sibling::*[0]
会话和cookie

无状态http

爬虫代理

免费、付费

  • 高度匿名代理
    会将数据包原封不动转发,服务端记录的是代理服务器的ip
  • 普通匿名代理
    代理服务器通常会加入http头 HTTP_VIA HTTP_X_FORWARD_FOR,可能能查到客户端的IP

urllib库

urllib是基于http的高层库,它有以下四个主要功能:

  1. request处理理客户端的请求
  2. response处理理服务端的响应
  3. parse会解析url
  4. 主要用来识别网站的robots.txt⽂文件,用得较少

获取响应信息

# 获取网页内容
import urllib.
request response = urllib.request.urlopen('http://www.baidu.com/') 
html = response.read().decode("utf-8") print(html)

# 取响应状态码和头信息 
print(response.status) 
print(response.getheaders()) 
print(response.getheader("Server"))

设置超时时间

import urllib.
request response = urllib.request.urlopen("http://2018.sina.com.cn/", timeout=1) html = response.read().decode("utf-8") 
print(html)

设置请求头和参数

from urllib import request, parse
url = "http://2018.sina.com.cn/" 
headers = {  "User-Agent": "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; 360SE)",  "Host": "2018.sina.com.cn", } 
dict = {  "name": "Question" } data = bytes(parse.urlencode(dict), encoding="utf8") req = request.Request(url=url, data=data, headers=headers, method="GET") response = request.urlopen(req) 
print(response.read().decode("utf-8"))

异常处理

from urllib import request, error
try:  
  response = request.urlopen("https://cuiqingcai.com/index.htm") 
except error.URLError as e:  
  print(e.reason)

用爬虫下载图片
pip install request

import requests
r = requests.get("http://www.baidu.com") 
print(r.status_code) 
print(r.text) 
print(r.cookies)

解析库的使用

XPath

XML Path Language XML 路路径语⾔言

安装lxml库 (支持HTML和XML解析,支持XPath解析方式)
pip3 install lxml

Beautiful Soup

pip3 install beautifulsoup4

解析器
  • Python 标准库 BeautifulSoup(html, “html.parser”) 速度一般,容错能力好
  • lxml HTML解析器 BeautifulSoup(html, “lxml”) 速度快,容错好
  • lxml xml解析器 BeautifulSoup(markup, “xml”) 速度快,唯一支持xml
  • html5lib BeautifulSoup(markup, “html5lib”) 容错性高,速度慢

引入BeautifulSoup

from bs4 import BeautifulSoup

获取方法

soup = BeautifulSoup(html, "lxml") # 试⽤用lxml解析器器构造beautifulsoup print(soup.prettify())  # 取⽹网⻚页缩进格式化输出 
print(soup.title.string) # 取⽹网⻚页title内容 
print(soup.head) 
print(soup.p)

# 获取节点的名字 
print(soup.title.name) 
# 获取节点属性 
soup.img.attrs["src"]
print(soup.p.attrs) 
print(soup.p.attrs["name"]) 
print(soup.p["class"]) 
# 获取节点包含的内容 
print(soup.p.string)

嵌套选择
<head>
<title>this is title</title>
</head>

# soup的节点都为 bs4.element.Tag类型,可以继续选择 
print(soup.head.title.string)

关联选择
有些元素没有特征定位,可以先选择有办法定位的,然后以这个节点为准选择它的子节点、父 节点、兄弟节点等
<p class="p1"></p>
<p></p>
<p></p>

print(soup.p.contents) # 取p节点下面所有⼦节点列表 
print(soup.p.descendants) #取p节点所有子孙节点 
print(soup.a.parent) # 取父节点 
print(soup.a.parents) # 取所有祖先节点 
print(soup.a.next_sibling) # 同级下一节点 
print(soup.a.previous_sibling) # 同级上一节点 
print(soup.a.next_siblings) # 同级所有后面节点 
print(soup.a.previous_siblings) # 同级所有前面节点 
print(list(soup.a.parents)[0].attrs['class'])

方法选择器
根据属性和文本进行查找
<ul><li><li><ul>
<ul><li><li>jjj<li><li></ul>

print(soup.find_all(name="ul"))
for ul in soup.find_all(name="ul"):  
  print(ul.find_all(name="li"))  
  for li in ul.find_all(name="li"):    
    print(li.string)
soup.find_all(attrs={"id": "list-1"})

css 选择器器
<p id="p1" class="panel"><p class=""><p><p>

soup.select('.panel .panel_heading') 
soup.select('ul li') 
soup.select('#id1 .element')

使用Selenium爬取动态渲染页面

模拟浏览器操作爬取页面

安装Selenium
pip3 install selenium

安装ChromeDriver
ChromeDriver与Chrome对照表
ChromeDriver v2.41 (2018-07-27)—————Supports Chrome v67-69 ChromeDriver v2.40 (2018-06-07)----------Supports Chrome v66-68
ChromeDriver v2.39 (2018-05-30)----------Supports Chrome v66-68
ChromeDriver v2.38 (2018-04-17)----------Supports Chrome v65-67
ChromeDriver v2.37 (2018-03-16)----------Supports Chrome v64-66
ChromeDriver v2.36 (2018-03-02)----------Supports Chrome v63-65

将chromdriver.exe 文件放到Python Scripts目录下或配置PATH路径命令行执行chromedriver

from selenium import webdriver 
brower = webdriver.Chrome()

selenium.webdriver.common.by.By
CLASS_NAME = 'class name'
CSS_SELECTOR = 'css selector'
ID = 'id' LINK_TEXT = 'link text'
NAME = 'name'
PARTIAL_LINK_TEXT = 'partial link text'
TAG_NAME = 'tag name'
XPATH = 'xpath'

验证码识别

图形验证码

image.png

tesseerocr
tesseract a.jpg result -l eng && cat result.txt
识别率低

识别验证码平台(打码平台)
超级鹰

验证码类型与价格表-超级鹰验证码识别

极验滑动验证码

step1. 模拟点击验证按钮
step2. 识别滑动缺⼝口位置 遍历没有缺⼝口和有缺⼝口的两张图⽚片,找出相同位置像素差距超过指定值的像素点,即缺⼝口位置 (⽬目前极验已经改进了了算法)
step3. 模拟拖动滑块


image.png

image.png

代理

image.png

同一ip单位时间访问次数过多,被封ip,需要借助代理伪装ip进行爬取

urllib, requests, selenium都可设置代理

代理池

提前筛选,保留可用的代理放在代理池中备用
存储模块、获取模块、检测模块、接口模块

  • 存储模块: 存储抓取下来的代理,不重复,使用redis sorted set,有序集合
  • 获取模块: 定时抓取各代理网站代理
  • 检测模块: 检测代理是否有效并设置分数,检测链接设为目标网站链接
  • 接口模块: 可提供代理接口
  • 分数规则: 分数100为可用,定时循环检测每个代理使用情况,一旦检测到可用代理设置为100, 不可用时分数减1,分数减为0后移除
    新获取的代理分数为10, 如测试可行,分数设为100,不可行分数减1,分数减为0后代理移除
    http://127.0.0.1:5555
    http://127.0.0.1:5555/random

付费代理

image.png

Scrapy 爬虫框架

特点:爬取效率高、扩展性强、Python编写跨平台运行

数据流程

Scrapy中的数据流由执行引擎控制,其过程如下:
1、引擎打开一个网站(open a domain),找到处理该网站的Spider并向该spider请求第一个要爬取的URL(s)。
2、引擎从Spider中获取到第一个要爬取的URL并在调度器(Scheduler)以Request调度。
3、引擎向调度器请求下一个要爬取的URL。
4、调度器返回下一个要爬取的URL给引擎,引擎将URL通过下载中间件(请求(request)方向)转 发给下载器(Downloader)。
5、一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件(返回 (response)方向)发送给引擎。
6、引擎从下载器中接收到Response并通过Spider中间件(输入方向)发送给Spider处理。
7、Spider处理Response并返回爬取到的Item及(跟进的)新的Request给引擎。
8、引擎将(Spider返回的)爬取到的Item给Item Pipeline,将(Spider返回的)Request给调度器。
9、(从第二步)重复直到调度器中没有更多地request,引擎关闭该网站。

安装Scrapy

pip install scrapy -i https://pypi.douban.com/simple

创建项目

scrapy startproject 项目名称
防止被爬网站的robots.txt起作用 ,settings.py ,修改 ROBOTSTXT_OBEY = False

创建Spider

scrapy genspider 项目名 爬取网页网址
Spider 属性
name: Spider名字
allowed_domains: 允许爬取的域名
start_urls: Spider启动时爬取的url列表
parse: 负责解析返回的响应,提取数据或进一步处理

创建Item

Item是保存爬取数据的容器

解析Response

使用Item

后续Request

运行

scrapy crawl 项目名

保存到文件 (csv, xml, pickle, marshal)

scrapy crawl football -o football.json

Scrapy 爬虫

Selector 是 Scrapy的选择器器,基于lxml构建,支持xpath, css, 正则表达式匹配

scrapy shell https://bbs.hupu.com/gear
result = response.selector.xpath("//a[@class='truetit']/text()")
type(result)
result = response.selector.xpath("//a[@class='truetit']/text()").extract()
result

SelectorList 和 Selector都可继续调用xpath()和css() 方法

result = response.selector.xpath("//div[contains(@class, 'author')]") type(result)
result.xpath('./a[1]/text()').extract() result.xpath('./a[1]/text()').extract_first() result.xpath('./a[1]/text()')[0] result.xpath('./a[3]/text()').extract() result.xpath('./a[3]/text()').extract()[0] result.xpath('./a[3]/text()').extract_first()

extract()[0] 与 extract_first()区别

result = response.css('.author.box').xpath('./a[2]/text()').extract()
result = response.css('.endreply.box a::text').extract() result = response.css('.endreply.box a::attr(href)').extract()
result = response.css('.author.box').xpath('./a[2]/text()').re('(.*?)-.*?-')

Spider

1、name:爬虫的名字。
2、allowed_domains:允许爬取的域名,不在此范围的链接不会被跟进爬取。
3、start_urls:起始URL列列表,当我们没有重写start_requests()方法时,就会从这个列表开始 爬取。
4、custom_settings:用来存放蜘蛛专属配置的字典,这里的设置会覆盖全局的设置。
5、crawler:由from_crawler()方法设置的和蜘蛛对应的Crawler对象,Crawler对象包含了很 多项目组件,利用它我们可以获取项⽬目的配置信息,如调用crawler.settings.get()方法。
6、settings:用来获取爬虫全局设置的变量。
7、start_requests():此方法用于生成初始请求,它返回一个可迭代对象。该方法默认是使用 GET请求访问起始URL,如果起始URL需要使用POST请求来访问就必须重写这个⽅方法,发送 POST请求使用FormRequest方法
8、parse():当Response没有指定回调函数时,该方法就会被调用,它负责处理Response对象并返回结果,从中提取出需要的数据和后续的请求,该方法需要返回类型为Request或Item 的可迭代对象(⽣生成器器当前也包含在其中,因此根据实际需要可以用return或yield来产⽣生返回 值)。
9、closed():当蜘蛛关闭时,该方法会被调用,通常用来做一些释放资源的善后操作。

Downloader Middleware

1、调度器将Request发给Downloader下载之前,可以对Request进行修改 process_request(request, spider)
2、下载后生成的Response发给Spider之前,可以对Response进行修改 process_response(request, response, spider)
3、Downloader或process_request()方法异常 process_exception(request, exception, spider)

Pipeline

Image pipeline
get_media_requests(self,item, info):
ImagePipeline根据image_urls中指定的url进行爬取,可以通过get_media_requests为每个url生成一个Request。如:

for image_url in item[‘image_urls’]:            
  self.default_headers[‘referer’] = image_url            
  yield Request(image_url, headers=self.default_headers)

item_completed(self, results, item, info):
图片下载完毕后,处理结果会以二元组的方式返回给item_completed()函数。这个二元组定义 如下: (success, image_info_or_failure) 其中,第一个元素表示图片是否下载成功;第二个元素是一个字典。如:

def item_completed(self, results, item, info):        
  image_paths = [x[‘path’] for ok, x in results if ok]        
  if not image_paths:            
    raise DropItem(“Item contains no images”)        
  item[‘image_paths’] = image_paths        
  return item

分布式爬虫

分布式爬虫是将多台主机组合起来,共同完成爬取任务


单机爬虫

分布式爬虫

爬取队列:使用Redis列表或有序集合 (默认是redis有序集合)
去重:使用Redis集合保存Request的指纹,提供重复过滤
中断续爬:调度器从Redis队列中取上次没有爬的继续爬取

使用分布式

安装scrapy-redis
pip install scrapy-redis
mongodb

show dbs; 
use yaoqi 
db.getCollectionNames() 
db.yaoqis.count() 
db.yaoqis.find()

redis
有序集合,在集合的基础上,为每元素排序;元素的排序需要根据另外一个值来进行比较,所以,对于有序集合,每一个元素有两个值,即:值和分数,分数专门用来做排序。

exists comic:requests 
exists comic:dupefilter
ZRANGE comic:requests 0 -1 withscores 
zcount comic:requests 0 -1

推荐阅读更多精彩内容