爬虫可以简单分为几步:抓取页面、分析页面、存储数据。
在第一步的抓取页面的时候,我们需要模拟成浏览器的样子来向服务器发送请求,所以需要用到Requests、Selenium、Aiotttp 等第三方库来实现HTTP请求:
1. Selenium: 这是一个自动化测试工具,我们可以使用它执行一些特定的动作比如点击,下拉等操作,特别是对于JS渲染的页面,该方法会非常有效。
2. ChromeDriver: 由于Selenium是一个自动化测试工具,所以我们需要浏览器来配合使用,我们需要ChromeDriver来驱动chrome完成相应的操作,下载后,需要将可执行文件配置到环境变量或将文件移动到属于环境变量的目录里:sudo mv chromedriver /usr/bin
from selenium import webdriver
browser = webdriver.Chrome()
执行之后会弹出一个空白的chrome浏览器,就ok
3. PhantomJS: 无界面可脚本编程的浏览器引擎,因为我们使用chrome或者Firefox的时候,每次抓取都会弹出一个浏览器,比较麻烦。
4. Aiohttp: Requests库是一个阻塞式HTTP请求库,发出请求后,程序会一直等待服务器的响应。Aiohttp可以提供异步请求来抓取数据并提高效率
===========================================================
第二步从网页中提取信息的时候,提取信息的方式有很多,可以使用正则来提取,但是写起来会很繁琐,我们还有很多强大的解析库:如 LXML、BeautifulSoup、PyQuery 等等。
1. LXML、BeautifulSoup: 支持 HTML 和 XML 的解析,支持 XPath 解析方式
2. PyQuery: 它提供了和 jQuery 类似的语法来解析 HTML 文档,支持 CSS 选择器
3. Tesserocr: Tesserocr 是 Python 的一个 OCR 识别库,在爬虫的过程中会遇到各种各样的验证码,大多数是图形验证码,我们可以使用OCR技术将其转换成电子文本。
=============================================================
爬虫开发过程中我们还会使用Flask这样的 Web 服务程序来搭建一些 API 接口,比如维护一个代理池,代理保存在 Redis 数据库中,我们要将代理池作为一个公共的组件使用。通过 Web 服务提供一个 API 接口,我们只需要请求接口即可获取新的代理。
================================================================
爬虫框架Scrapy: 平时我们直接使用Requests、Selenium 等库写爬虫,如果爬取量不是很大,可以满足需求,但是写多了就会发现很多代码是可以复用的,我们将这些可复用的功能模块话就得到了一个框架雏形。使用框架我们不需要关心某些功能的具体实现,只需要关心逻辑,主要的爬虫框架有PySpider和Scrapy。
============================================================
分布式爬虫: 如果想要大规模抓取数据,就会用到分布式爬虫,所以我们需要多台主机,每台主机执行多个爬虫任务,但是源代码其实只有一份。那么我们需要做的就是将一份代码同时部署到多台主机上来协同运行。
爬虫基础
HTTP基本原理:
URI为统一资源标志符,URL为统一资源定位符,URI其实包含了两个子集,一个是URL,另一个是URN:统一资源名称,不指定如何定位资源。但是现在URN使用很少,所以几乎所有的URI都是URL。
在chrome中使用F12再点击network我们可以看到http请求报文,这里我们爬虫主要是需要Request,Request Header请求头用来说明服务器要使用的附加信息,所以是重要组成部分;Request Body一般承载的内容是 POST 请求中的 Form Data,即表单数据,而对于 GET 请求 Request Body 则为空。对于response, 我们做爬虫请求网页后要解析的内容就是解析响应体Response Body
=============================================================
session和cookie:
HTTP是无状态的,即对事务处理是没有记忆能力的,所以如果后续需要处理需要前面的信息,则它必须要重传,这也导致了需要额外传递一些前面的重复 Request 才能获取后续 Response。然后我们有两个保持HTTP连接状态的技术:session和cookie。Session 在服务端,用来保存用户的会话信息,Cookies 在客户端。所以我们一般会直接将登录成功后获取的 Cookies 放在 Request Headers 里面直接请求,而不必重新模拟登录。
在 Web 中 Session 对象用来存储特定用户会话所需的属性及配置信息。Cookie指某些网站为了辨别用户身份、进行 Session 跟踪而储存在用户本地终端上的数据。
我们在登录某网站的时候,登陆成功后服务器会告诉客户端设置哪些cookies信息,在后面访问的过程中,客户端会把cookies发送给服务器,服务器需要找到对应的session加以判断,如果这个session中某些设置登录状态的变量是有效的,那就证明用户是处于登录状态的,所以session和cookies协同合作,实现了会话登录。
使用F12,点击Application选项卡,里面Storage的最后一项就是cookies。
=============================================================
代理:Proxy server
有时候爬虫爬一会儿就出现”您的 IP 访问频率太高“这样的错误提示,这是因为这样的网站有反爬虫措施,服务器会检查某个ip在单位时间内请求的次数,如果超过阀值就会报错。有效的应对方式是使用代理,伪装IP
代理的原理就是本机和服务器之间搭建了一个桥梁,本机向代理服务器发起请求,代理服务器再发给web服务器。
使用基本的爬虫库
使用Urllib
他是python内置的http请求库,用来实现request的发送,一共有四个模块:
- request: 可以用来模拟发送请求,只需要给库方法传入URL还有额外的参数,就可以模拟输入网址的过程
- error:如果出现请求错误,可以捕获这些异常
- parse: 提供了许多URL的处理方法,拆分,解拆,合并等方法
- robotparse: 用来识别网站的robot.txt文件,然后判断哪些网站可以爬或者不可以爬
1. 发送请求:urlopen() 基本不直接这样用
import urllib.request
response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))
真正的代码只有两行,我们就爬取到了python官网的源代码,得到的response是一个HTTPResposne 类型的对象。它主要的属性和方法有:
- read(): 返回网页的内容
- readinto()
- getheader(name)
- getheaders():响应的头信息
- fileno()
- msg, version, status, reason, debuglevel, closed
如果要给这个链接传递一些参数怎么办,我们先看一下urlopen()函数的API:
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)
data参数:
如果要添加data,需要时字节流编码格式,所以需要转化,请求方式也不能是get了,需要是post:
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
# urlencode方法将参数字典转换成了字符串
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
# 这个网站可以用来测试post请求,可以输出request的信息
print(response.read())
timeout参数:
import socket
import urllib.request
import urllib.error
try:
response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
if isinstance(e.reason, socket.timeout):
print('TIME OUT')
这个参数单位为秒,如果超出这个时间没有得到响应,就抛出异常,所以我们可是使用try except来控制如果一个网页长时间未响应那么就跳过。
========================================================
2. 发送请求:Request 使用这个比上面好
使用urlopen()可以实现在基本的请求,但是这几个简单的参数不足以满足需求,所以我们使用更强大的 Request类来构建请求。
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
虽然我们还是使用urlopen(),但是我们没有直接传入URL参数,而是一个对象,这样对象为可配置:
class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)
- 第一个为URL:唯一必选参数
- 第二个为data: 字节流类型可传参数,要用urlencode()编码
- 第三个为headers:也可以通过add_header()方法来添加,最常用的方法就是修改user-agent来伪装浏览器,下面有例子
- 第六个为method: 指定使用的请求方式,GET,POST…
例子:
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
'Host': 'httpbin.org'
}
dict = {
'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))
3. 高级用法
如果有一些更高级的操作,比如cookies处理,代理设置。我们就需要Handler这个强大的工具,它相当于各种强大的处理器,有专门验证登陆的,处理cookies的等等,但是这里不详细介绍,因为我们后面会有更强大的工具
4. 处理异常:
URLError类是这个库的error模块,它具有一个属性reason,即错误的原因:
try:
response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.URLError as e:
print(e.reason)
HTTPError是URLError的子类,专门处理请求错误,比如认证请求失败等,它有三个属性:
- code:返回状态码
- reason:错误原因
- headers:返回request headers
5. 解析链接:重要!!
urllib库还提供了parse这个模块,定义了处理URL的标准接口:
- urlparse() 实现URL的分段和识别
from urllib.parse import urlparse
result = urlparse('http://www.baidu.com/index.html;user?id=5#comment')
print(type(result), result)
==========
<class 'urllib.parse.ParseResult'>
ParseResult(scheme='http', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')
可以看到这个方法将URL拆成了六个部分,返回的结果实际上是一个元组,可以用属性名获取也可以使用索引顺序获取。
- urlunparse() 实现URL的组合
接受的参数是一个可迭代对象,长度必须为6,否则会报错:
from urllib.parse import urlunparse
data = ['http', 'www.baidu.com', 'index.html', 'user', 'a=6', 'comment']
print(urlunparse(data))
====================
http://www.baidu.com/index.html;user?a=6#comment
-
urlsplit()
和urlparse很像,只是不会单独解析parameters,会合并到path当中,所以只返回五个结果 -
urlunsplit()
和urlunparse非常的相似,只是接受长度为5 -
urljoin() 生成链接
前面说的两个方法都要特定的长度,这个方法提供一个基础链接,新的链接作为第二个参数,方法会分析 base_url 的 scheme、netloc、path 这三个内容对新链接缺失的部分进行补充。
print(urljoin('http://www.baidu.com', 'FAQ.html'))
http://www.baidu.com/FAQ.html
- urlencode() 非常常用,将字典序列化为URL标准GET请求参数
params = {
'name': 'germey',
'age': 22
}
base_url = 'http://www.baidu.com?'
url = base_url + urlencode(params)
print(url)
>> http://www.baidu.com?name=germey&age=22
- parse_qs() 反序列化
query = 'name=germey&age=22'
print(parse_qs(query))
>> {'name': ['germey'], 'age': ['22']}
-
parse_qsl() 反序列化
跟上面的方法一样,只是转换成元组组成的列表 -
quote() 将中文字符转化为URL编码
keyword = '壁纸'
url = 'https://www.baidu.com/s?wd=' + quote(keyword)
print(url)
>> https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8
- unquote() 反编码
跟上面的方法作用相对
6. Robots协议:
这个协议告诉爬虫哪些页面可以爬取,哪些不可以:
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()
rp.set_url('http://www.jianshu.com/robots.txt')
rp.read()
print(rp.can_fetch('*', 'http://www.jianshu.com/p/b67554025d7d'))
print(rp.can_fetch('*', "http://www.jianshu.com/search?q=python&page=1&type=collections"))