**(1)**企事业单位生产运营、工作发展产生大量数据
(2) 数据的价值化:通过对海量数据进行分析,能够产生极大的商业价值。
(3) 数据市场缺乏期望的数据或者数据的价格太高而难以负担,则需要自行获取。
**网络爬虫(又称: 网络蜘蛛、网络机器人)**是一种按照一定规则、自动请求万维网网站并获取网络公开的可安全访问的数据的程序或脚本。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1xIxj0dx-1575726189581)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\爬虫的分类.jpg)]
如果看不清请点击:https://mubu.com/doc/dz1LG9bFyw
简述如下:
若图片无法显示,请点击(Alt+Click):http://assets.processon.com/chart_image/5c82218fe4b0f88919a69dc6.png?_=1552032463149
数据获取要讲礼貌。在爬取目标网站之前,爬虫通过网站自身提供的robots.txt 和 Sitemap.xml 文件了解网站的结构,防止爬取目标网站不希望公开的内容。
下面简述两文件的作用及其相关内容
robots.txt(符合Robots 协议 (又称:爬虫协议、机器人协议等,全称:“网络爬虫排除标准”))是爬虫访问目标网站时查看的第一个文件,他会限定网络爬虫的访问范围。如果文件存在,则爬虫会按照该文件的内容确定访问的范围,否则,爬虫能访问所有没有密码保护的页面。
文件记录的详细介绍 :
记录 | 作用 |
---|---|
User-Agent | 描述搜索引擎(爬虫)的名字,至少一个。若其值为“ * ”,则表示该协议对任何搜索引擎都有效,且这样的记录只有一条 |
Disallow | 描述不希望被访问的URL,可以是完整路径,也可以是部分路径。任何一条该类记录为空,说明该网站的所有部分都可访问。在robots.txt文件中,至少有一条该类记录 |
Allow | 描述希望被访问的URL,与 Disallow 类似。一个网站的所有 URL 默认Allow,所以通常与 Disallow 搭配使用,实现允许访问已备份网页的同时,禁止访问其他所有 URL 的功能 |
该文件的作用 :方便网站管理员同志爬虫便利和更新网站内容,无须爬取每个网页。
在该文件中,列出了网站中的网址及每个网址的其他元数据。例如: 上次更新的时间、更改的频率及相对于网站上其他网址的重要程度。
User-Agent表示用户代理,是HTTP协议中的一个字段,其作用是描述发出HTTP请求的诸如操作系统、浏览器及其版本等的终端信息,通过该字段,服务器克制访问网站的用户。
如果网站根据某时间段内IP访问次数来判定是否为爬虫,一旦这些IP被封,User-Agent将失效。该情况下,可使用代理IP完成。
如果没找到免费稳定的代理IP,可降低访问频率,从而防止爬虫被对方认出。
有些网站对于异常访问量,会提出附带验证码的登录要求。该情况需要采用相应技术识别验证码,从而正确输入验证码,继续爬取网站
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BDIBt4ZK-1575726189583)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\抓取策略与去重.png)]
(1) 浏览器通过DNS服务器查找域名对应的IP地址
**(2) ** 向IP地址对应的Web服务器发送请求
(3) Web服务器响应请求,发回HTML页面
**(4) ** 浏览器解析HTML内容,并显示出来
统一资源定位符 ( Uniform Resource Locator,URL )是互联网上标准资源的地址,互联网上每个文件(即:资源)都有唯一的URL,它包含问价的位置及浏览器处理方式等信息 。
其组成如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IPNfPg0o-1575726189583)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\URL示意.jpg)]
几个部分详解如下:
协议头(Protocol Head)指定使用的传输协议,用于向浏览器解释如何处理打开文件的方式。不同的协议表示不同的资源查找及其传输方式。
常见的协议如下:
常见协议 | 代表类型 |
---|---|
File | 访问本地计算机的资源 |
FTP | 访问共享主机的资源 |
HTTP | 超文本传输协议,访问远程网络资源,明文传输 |
HTTPS(HTTP+SSL) | 安全的SSL加密传输协议,访问远程网络资源 |
Mailto | 访问电子邮件地址 |
服务器地址(Hostname 或 IP) 指存放资源的服务器的主机名或IP地址,其目的在于标识互联网上的唯一一条计算机,并通过这个地址找到这台计算机。
**端口(Port)**是在地址和冒号后的数字,用于标识一台计算机上运行的不同程序。每个网络程序都对应一个或多个特定的端口号
**路径(path)**是由0个或多个“/”符号隔开的字符串
1. DNS是计算机域名系统(Domain Name System 或 Domain Name Service )的缩写,它可以把域名转换为对应的IP地址。
请求格式包含以下几个部分:
GET https://www.baidu.com/ HTTP/1.1
#请求方法 URL地址 协议版本
几个常见的请求方法:
方法 | 描述 |
---|---|
GET | 请求指定的页面信息,并返回实体主体 |
POST | 向指定资源提交数据进行处理请求(如 提交表单或上传文件),数据被包含在请求体中.POST请求可能会导致新的资源建立和已有资源的修改 |
HEAD | 用于请求由Request-URI所标识的资源的响应消息报头 |
PUT | 请求服务器存储一个资源,并用Request-URI作为其标识 |
DELETE | 请求服务器删除Request-URI所标识的资源 |
CONNECT | 将服务器做跳板,预留给能够将连接改为管道方式的代理服务器,让服务器代替客服端访问其他网页 |
OPTIONS | 允许客户端查看服务器的性能 |
TRACE | 回显服务器收到的请求,主要用于测试或诊断 |
注: GET和 POST 两种请求方式最常用,区别在于:
(1) GET是从服务器上获取指定页面信息,POST是向服务器提交数据并获取页面信息.
(2) GET请求参数都显示在URL上,服务器根据该请求所包含URL中的参数来产生响应内容,请求参数因暴露在外而安全性不高,不存在请求体。
(3) POST请求参数在请求体中,消息长度没限制且采用隐式发送,通常用来向HTTP服务器提交量大的数据(请求中包含许多参数或文件上传操作等)。由于参数包含在请求体中,故安全性较高。
请求行下是若干请求报头,下面介绍常用的请求报头及其含义:
(1) Host(主机和端口号) : 制定被请求资源的Internet主机和端口号。
(2)Connection(连接类型) :表示客户端与服务器之间的连接类型.
**(3)Upgrade-Insercure-Requests(升级为HTTPS请求) **:表示升级不安全的请求,会在记载HTTP资源时自动替换成HTTPS请求,让浏览器不再显示HTTP请求警报。HTTPS是以安全为目标的HTTP通道,所以在HTTPS承载的页面上不允许出现HTTP请求,一旦出现九尾提示或者报错。
(4)User-Agent(用户代理) :标识客户端身份的名称。通常页面根据不同的Agent信息自动做出适配,甚至返回不同的响应内容
(5)Accept(传输文件类型) :指浏览器或其他客户端可以接受的MIME(Multipurpose Internet Mail Extensions,多用途因特网邮件扩展) 文件类型,服务器可以根据它判断并返回适当的文件格式.
其中:
**q *权值,范围[0,1] 值越大,越倾向于得到“ ; ”之前类型表示的内容.默认为1,按从左到右排序;若赋值为0,则表示浏览器不接受此内容类型.
text : 用于标准化地表示文本信息,文本信息可以是多种字符集合多种格式
Application : 用于传输应用程序数据或者二进制数据
其他MIME文件类型可自行了解
(6)Referer(页面跳转来源) :表明产生请求的网页来自于哪个URL,用户是从该Referer页面访问到当前请求的页面。这个属性可用来跟踪Web请求来自哪个页面,是从什么网页来的等信息。
用途 : 防盗链
(7)Accept-Encoding(文件编解码格式) :指出浏览器可接受的编码方式,如果请求消息中没设置该报头,通常服务器嘉定客户端不支持压缩,直接返回文本。
(8)Accept-Language(语言类型) :指出浏览器可接受的语言种类
(9)Accept-Charset(字符编码) : 指出浏览器可接受的字符编码
(10)Cookie(Cookie) : 浏览器用这个属性向服务器发哦是那个Cookie
(11)Content-Type(POST 数据类型) : 指定POST请求中用来表示的内容类型.
这部分当前还未发现其作用,故不写
响应状态码由3位数字组成,第一位定义响应类别,其作用用于初步确定网络连通情况.
常用的相应状态码如下所示:
状态码范围 | 含义 |
---|---|
100~199 | 服务器成功接收部分请求,要求客户端继续提交其余请求才能完成整个处理过程 |
200~299 | 表示服务器成功接收请求并已完成整个处理过程,常见状态码为 200(请求成功) |
300~399 | 为完成请求,客户需进一步细化请求 |
400~499 | 服务器请求有错误.常见的有404(服务器无法找到被请求的页面) 403(服务器拒绝访问,权限不足) |
500~599 | 服务器端出现错误.常用状态码: 500(请求未完成,服务器遇到不可预知的情况) |
静态网页就是不使用Js等动态技术呈现的网页
爬取该类网页常用的库有Python内置库 urllib 和 第三方库 requests
以下详解两个库的用法
urllib是Python内置的HTTP请求库,它可看作处理URL的组件集合。urllib 库包含四大模块
模块 | 说明 |
---|---|
urllib.request | 请求模块 |
urllib.error | 异常处理模块 |
urllib.parser | URL解析模块 |
urllib.robotparser | robot.txt解析模块 |
其中,最常用的是 urllib.request 模块
**urlopen() **方法是 urllib 库抓取静态网页的常用方法。其格式与属性详解如下:
from urllib import request
response = urlopen(url,data = None,[timeout]*,cafile=None,capath=None,cadefault=None,context=None)
'''注:1.response为随便一个后期方便使用的变量
模块的调用与方法的使用也可以使用如下形式
from urllib.request
response = urllib.request.urlopen(...)'''
属性详解如下:
属性 | 说明 |
---|---|
url | 表示目标资源在网站中的位置,可以是表URL的字符串,也可是urllib.request对象 |
data | 指明向服务器发送请求的额外信息。HTTP协议是python支持的唯一一个使用data参数的通信协议.data必须是bytes对象,使用 parse.urlencode() 函数(只接受mapping类型或只包含两个元素的元组类型)可以将自定义的data转换为标准格式。data默认为None,当设置了data参数后,需要使用POST 请求方式 |
timeout | 可选参数,用于设定超时时间,单位为 秒 |
cafile/capath/cadefault | 用于实现可信任的CA证书的HTTPS请求,很少使用 |
context | 实现SSL加密传输,很少使用 |
使用urlopen() 方法发送HTTP请求后,服务器返回的响应内容封装在一个HTTPResponse类型的对象中。HTTPResponse类属于http.client 模块,该类提供了获取URL、状态码、响应内容等方法,常见方法如下:
方法 | 说明 |
---|---|
geturl() | 用于获取响应内容的URL,该方法可验证发送的HTTP请求是否被冲新调配 |
info() | 返回页面的元信息 |
getcode() | 返回HTTP请求的响应状态码 |
当使用urlopen() 方法发送一个请求是,如果希望执行更复杂的操作(如:增加HTTP报头等,最好执行复杂的操作),必须创建一个Request对象来作为 urlopen() 方法的参数。在发送请求时,除了必须设置的URL参数,还可以添加很多内容。例如:
参数 | 说明 |
---|---|
data | 默认为空,该参数表示提交表单数据,同时HTTP请求方法改为POST |
headers | 默认为空,该参数为字典类型,包含需要发送的HTTP报头的键值对 |
构造对象示例如下:
from urllib import request,parse
url = 'http://www.itcast.cn'
headers={
'User-Agent':'Mozilla/5.0 (comoatible;MSIE 9.0;windows NT6.0;Trident/5.0)',
'Host':'httpbin.org'
}
dict_demo = {'name':'itcast'}
data = bytes(parse.urlencode(dict_demo).encode('utf-8'))
requestcontent = request.Request(url,headers=headers,data=data)
#在Request方法中添加url headers报头信息 data属性
response = request.urlopen(requestcontent)
html = response.read().decode('utf-8')
print(html)
当传递的URL包含中文或其他特殊字符(如: 空格或‘/’)时,需要使用urllib.parse库中的urlencode() 方法将URL进行编码.其结果是将’key: value’ 转换为 ‘key=value’
反之,解码使用的是 **urllib.parse **库中的 unquote() 方法
注 : 编码转换常用于数据和信息直观可见字符与URL编码格式之间的转换
有些网站在输入并提交表单数据后,URL不发生变化。审查元素会发现,在提交表单数据后,网络传输数据中出现JSON等数据,可以通过模拟该类数据形式获取相应的数据信息。
(如: 有道在线翻译,输入 ‘Python’ 后,URL未发生变化,但网页传输的数据中出现Json数据)
对于需要登陆的网站,如果不是从浏览器发出请求,不会得到响应内容。所以针对这种情况,需要对爬虫程序通过以定制请求头为主的多种形式进行伪装。使用urllib库添加/修改Headers的方式是调用Request.add_header() 方法.
如果需要查看已有的Headers,调用 Request.get_header() 方法
当使用 urlopen() 方法发送HTTP请求时,如果 urlopen() 不能处理返回的响应内容,就会产生错误。常见的错误及其错误结果如下
(1) 没有连接网络
(2) 服务器连接失败
**(3) **找不到指定的服务器
使用try……except 语句捕获相应的异常
import urllib.request
import urllib.error
request = urllib.request.Request('http://www.xiaoqiming.com')
try:
urllib.request = urlopen(request,timeout=5)
except urllib.error.URLError as err:
print(err)
每个服务器的HTTP响应都会有一个数字响应码,这些响应码有些表示无法处理的请求内容。如果无法处理,urlopen() 会抛出HTTPError.HTTPError是URLError的子类,他的对象拥有一个整型的code属性。表示服务器返回的错误代码。例如
import urllib.request
import urllib.error
request = urllib.request.Request('http://www.xiaqiming.com')
try:
urllib.request.urlopen(request)
except urllib.error.HTTPError as e:
print(e.code)
requests 库是基于Python 开发的第三方HTTP库.相比于urllib库,它使用方便简洁.实际上, requests ** 库在urllib库的基础上尽享了高度封装,不仅继承后者所有特性,还支持一些其他特性,比如: 使用Cookie** 保持会话/自动确定响应内容的编码等.
**requests **库中提供了如下常用的类:
类 | 说明 |
---|---|
requets.Request | 表示请求对象,用于将请求发送到服务器 |
requests.Response | 表示响应对象,其中包含服务器对HTTP请求的响应 |
requests.Session | 表示请求会话,提供Cookie持久性、连接池和配置 |
注 : 关于Request 类 与 Session 类的区别
Request类的对象表示一个请求,它的声明周期针对一个客户端请求,一旦发送完毕,该请求的内容就会被释放掉。
Session类的对象可跨越多个页面,它的生命周期同样针对一个客户端。当关闭这个客户端的浏览器时,只要是在预先设置的会话周期内(一般是20~30 min),这个会话包含的内容会一直在,不马上释放掉。例如:用户登录某网站时,可以在多个IE窗口发出多个请求。
requests 库中提供了很多发送HTTP请求的函数,具体如下:
函数 | 说明 |
---|---|
requests.request() | 构造一个请求,支撑以下各方法的基础方法 |
requests.get() | 获取HTML网页的主要方法,对应于HTTP的GET请求方式 |
requests.head() | 获取HTML网页头信息的方法,对应于HTTP的HEAD请求方式 |
requests.post() | 向HTML提交POST请求的方法,对应于HTTP的POST请求方式 |
requests.put() | 向HTML提交PUT请求的方法,对应于HTTP的PUT请求方式 |
requests.patch() | 向HTML网页提交局部修改请求,对应于HTTP的PATCH请求方式 |
requests.delete() | 向HTML网页提交删除请求,对应于HTTP的DELETE请求方式 |
Response 类用于动态地响应客户端的请求,控制发送给用户的信息,并且将动态地生成包括状态码、网页内容等响应。以下列举Response 类的常用属性
属性 | 说明 |
---|---|
status_code | HTTP请求的返回状态,对应响应状态码 |
text | HTTP响应内容的字符串形式,即URL对应的页面内容 |
encoding | 从HTTP请求结果的内容编码方式 |
apparent_encoding | 从内容中分析出的响应编码的方式(备选编码方式) |
content | HTTP响应内容的二进制形式 |
json | 将Http请求结果转为JSON格式 |
动态网页 是指网页中依赖JavaScript脚本动态加载数据的网页。与传统单页面表单事件不同,使用JavaScript脚本的网页能够在URL不变的情况下改变网页的内容。动态网页上使用的技术主要包括如下几种:
JavaScript是网络上最常用、支持者最多的客户端脚本语言。其常见形式如下:
<script type='text/javascript' scr='[jspath]'>script>
jQuery 是一个十分常见的Javascript库,其封装了Javascript常用的功能代码,提供简便的Javascript设计模式,优化HTML文档操作/事件处理等。jQuery最鲜明的特征是源码中包含jQuery入口。例如:
<script type='text/javascript' src='[path]/jquery-1.11.1.min.js'>script>
Ajax 是用来完成网络任务(可认为与网络数据采集)的一系列技术,而不是一门语言.Ajax能使网站不需要使用单独额页面请求就可以和网络服务器进行交互.
DHTML( 动态 HTML ) 也是一系列用于解决网络问题的技术集合.DHTML是用客户端语言改变页面的HTML元素。网页是否使用DHML关键看有没有使用Javascript控制Html和CSS元素
对于动态内容的爬取,用Python解决是有两种途径:
(1) 直接从Javascript 代码中采集内容(费时费力)
(2) 用Python 的第三方库运行Javascript,直接采集在浏览器中看到的页面。
selenium 是一个Web自动化测试工具,其直接运行在浏览器上,支持所有主流的浏览器(Chrome、Firefox等以及PhantomJS这类无界面浏览器),可按照命令实现诸如自动加载页面、获取数据、页面截屏、判断动作发生等操作
selenium与Firefox、chrome搭配使用。但是,需要为两个浏览器分别配置驱动环境变量(Firefox: geckodriver Chrome:chromedriver 该驱动需要与chrome匹配),下载链接(内含geckodriver和最新版chromedriver):
https://pan.baidu.com/s/1NfClf9ON_d0DIpP_s9RgNA 提取码:mjxt
#导入WebDriver
from selenium import webdirver
#调用环境变量指定的PhantomJS浏览器创建浏览器对象
driver = webdriver.PhantomJS()
#调用火狐浏览器创建浏览器对象
driver=webdriver.Firefox()
#如果没在环境变量中指定浏览器位置,需要传入浏览器文件(如:phantomjs.exe)所在的路径.
driver = webdriver.PhantomJS(executable_path='[path]')
#获取页面内容
driver.get('[url]')
#获取页面标题
driver.title
#生成当前页面快照并保存
driver.save_screenshot('[picname.png/picname.jpg]')
#获取页面源代码
driver.page_source
#在页面的输入框中添加内容(使用send_keys()方法)
driver.find_element_by_id('[输入框id属性值]').send_keys('[content]')
#模拟单击页面上的按钮(使用click()方法)
driver.find_element_by_id('[按钮id属性值]').click()
#清除输入框内容(使用clear() 方法)
driver.find_element_by_id('输入框id属性值').clear()
#获取当前页面Cookie
driver.get_cookies()
#获取当前URL
driver.current_url()
#关闭当前页面
driver.close()
#关闭浏览器
driver.quit()
#调用键盘按钮操作首先需要引入Keys 包
from selenium.webdriver.common.keys import Keys
#通过模拟[ctrl+A] 全选输入框的内容
driver.find_element_by_id('输入框id属性值').send_keys(Keys.CONTROL,'a')
#通过模拟[ctrl + X] 剪切输入框内容
driver.find_element_by_id('输入框id属性值').send_keys(Keys.CONTROL,'x')
#在输入框重新输入搜索关键字
driver.find_element_by_id('输入框id属性值').send_keys('[a new key]')
#模拟按[Enter]键
driver.find_element_by_id('输入框id属性值').send_keys(Keys.RETURN)
定位UI元素有两种方式,一种为直接使用方法定位方法来定位元素
另一种引入By模块,再使用find_element() 方法定位元素下面详细介绍两种方式:
#通过id标签值定位页面元素
driver.find_element_by_id('[id属性值]')
#通过name标签值定位页面元素
driver.find_element_by_name('name属性值')
#通过标签名定位页面元素
driver.find_element_by_tag('标签名')
#通过XPath定位页面元素
driver.find_element_by_xpath('xpath路径')
#通过链接文本定位页面元素
driver.find_element_by_link_text('[link_text]')
#通过部分链接文本定位页面元素
driver.find_element_by_partial_link_text('[partial_link_text]')
#通过CSS定位页面元素
driver.find_element_by_css_selector('[css属性定位]')
**注: ** 以下方法中的 by= 、value= 部分可以省略
#在使用该方法之前,要先引入By模块
from selenium.webdriver.common.by import By
#通过id标签值定位页面元素
element = driver.find_element(by=By.ID,value='[content]')
#通过name标签值定位页面元素
element = driver.find_element(by=By.NAME,value='content')
#通过标签名定位页面元素
element = driver.find_element(by=By.TAG_NAME,value='[content]')
#通过XPath定位页面元素
element = driver.find_element(by=By.XPATH,value='[content]')
#通过链接文本定位页面元素
element = driver.find_element(by=By.LINK_TEXT,value='[content]')
#通过部分链接文本定位页面元素
element = driver.find_element(by=By.PARTIAL_LINK_TEXT,value='[text]')
#通过CSS定位页面元素
element = driver.find_element(by=By.CSS_SELECTOR,value='[css属性定位]')
有的时候,需要在页面上模拟诸如双击、右击、拖动甚至按住不动等鼠标操作,可通过ActionChains类来实现。
#导入ActionChains类
from selenium.webdriver import ActionChains
#鼠标移动到元素位置
ac = driver.find_element_by_xpath('xpath路径') #确定元素位置,在实现鼠标动作前,都需要定位元素
ActionChains(driver).move_to_element(ac).perform()
#在元素位置双击
ActionChains(driver).move_to_element(ac).double_click(ac).perform()
#在元素位置右击
ActionChains(driver).move_to_element(ac).context_click(ac).perform()
#在元素位置左键单击并保持
ActionChains(driver).move_to_element(ac).click_and_hold(ac).perform()
#元素拖动,ac1→ac2
ActionChains(driver).drag_and_drop(ac1,ac2).perform()
当遇到标签的下拉列表框,直接点击下拉框的选项不一定可行。该情况可以使用Select类来处理。具体如下:
#导入Select 类
from selenium.webdriver.support.ui import Select
#找到下拉框元素
select = Select(driver.find_element_by_name('[下拉框属性值]'))
#选择下拉框的某一个选项
select.select_by_index(1) #根据索引选择
select.select_by_value('0') # 根据值选择
select.select_by_visible_text('[content]') #根据文字选择
#当触发某个事件,页面出现弹窗提示,处理这个提示或者获取提示信息,可使用浏览器对象的 switch_to_alert() 方法.
#示例如下:
alert = driver.switch_to_alert()
#一个浏览器会有多个窗口,所以需要有方法实现窗口的切换
#方法如下:
driver.switch_to.window('[window_name]')
#也可使用 window_handles() 方法来获取每个窗口的操作对象
#示例如下:
for handle in driver.window_handle():
driver.switch_to_window(handle)
driver.forward() #前进
driver.back() #后退
#使用 get_cookies() 方法获取页面上所有的Cookie
#示例如下:
for cookie in driver.get_cookies():
print("%s=%s;"%(cookie['name'],cookie['value']))
#删除Cookies 使用 delete_cookie('[cookie_name]')删除单条cookie
#使用delete_all_cookies() 删除所有cookies
由于现在的网页越来越多地使用Ajax技术,导致程序不能确定何时某个元素能被完全加载。若页面响应时间过长,导致元素未能出来就被代码引用,将导致NullPointer异常。
为解决该问题,selenium提供两种等待方式:一种为显式等待,一种为隐式等待。下面为详细介绍:
显式等待指定某个条件,然后设置等待时间,如果时间结束还未找到元素,则抛出异常。
显式等待使用** WebDriverWait **类,其构造函数定义如下:
webDriverWait(driver,timeout,poll_frequency=0.5,ignored_exceptions=None)
构造函数参数及其定义如下:
参数 | 说明 |
---|---|
driver | WebDriver的驱动程序(IE、Chrome、Firefox、PhantomJS等浏览器或远程) |
timeout | 最长超时时间,默认以秒为单位 |
poll_frequency | 休眠时间的间隔(步长)时间,默认为0.5 |
ignored_exceptions | 超市后的异常信息,默认情况下抛出NoSuchElementException异常 |
WebDriverWait 对象一般与until() 或 until_not() 方法配合使用,这两个方法介绍如下:
方法 | 说明 |
---|---|
until() | 调用该方法提供的驱动程序作为一个参数,知道返回值不为False |
until_not() | 调用该方法提供的驱动程序作为一个参数,直到返回值为False |
下面是一个使用WebDriverWait 对象的示例:
from selenium import webdriver
from selenium.webdriver.common.by import By
#WebDriverWait 库 负责循环等待
from selenium.webdriver.support.ui import webDriverWait
#expected_conditions 类,负责条件触发
from selenium.webdriver.support import expected_conditions as EC
driver = webdriver.PhantomJS()
driver.get('http://www.baidu.com')
try:
#查找页面元素 id='kw',直到出现则返回,如果超过十秒则报出异常
element=WebDriverWait(driver,10).until(EC.presence_of_element_located(Bu.ID,'kw'))
finally:
driver.quit()
下面是一些内置的等待条件,可以直接调用这些条件,而不用自己写等待条件:
title_is
title_contains
presence_of_element_located
visivility_of
text_to_be_present_in_element
text_to_be_present_in_element_value
frame_to_be_available_and_switch_to_it
invisibility_of_element_located
element_to_be_clickable - it is Displayed and Enabled.
staleness_of
element_to_be_selected
element_located_to_be_selected
element_selection_state_to_be
element_located_selection_state_to_be
隐式等待就是设置一个全局的最大等待时间,单位为秒。在定位元素时,对所有元素设置超时时间,超出设置时间则抛出异常。
隐式等待时间一旦设置,就会在WebDriver对象实例的整个生命周期起作用。
隐式等待使用 implicitly_wait() 方法,它使得WebDriver在查找一个 Element或者 Element 数组时,每隔一段特定时间会轮询一次DOM(文档对象模型,例如:HTML文件标签结构),知道Element或数组被发现为止。
实例如下:
from selenium import webdriver
driver=webdriver.PhantomJS()
driver.implicitly_wait(10) #设置等待时间
driver.get('[url]')
dynamic_element=driver.element_by_id('[id属性值]')
使用Selenium+浏览器爬取动态网页速度通常较慢,因为Selenium是在整个页面加载出来后才开始爬取内容。在实际使用过程中,如果使用浏览器的“检查”(Chrome中鼠标右键显示“检查”选项)功能进项网页的逆向工程不是很复杂,最好使用该功能。对于提升Selenium的爬取速度,通常采用以下几张方式:
方式 | 说明 |
---|---|
set_preference(“permissions.default.stylesheet”,2) | 控制CSS加载,1表示允许加载,2表示不允许加载 |
set_preference(“permissions.default.image”,2) | 控制图片加载,1表示允许加载,2表示不允许加载 |
set_preference(“javascript.enabled”,False) | 控制JavaScript加载,True允许,False不允许 |
注 : 以上方法在创建浏览器对象后使用。例如:在Chrome浏览器对象中限制CSS加载
from selenium import webdriver
from selenium.webdriver.firefox.firefox_binary import FirefoxBinary
caps = webdriver.DesiredCapabilities().FIREFOX#DesiredCapabilities()类用于在指定环境运行需要的脚本
caps['marionette'] = False
binary=FirefoxBinary(r'C:\Program Files\Mozilla Firefox\firefox.exe')#2、3 行用于设置火狐浏览器对象(Chrome浏览器等同理,用于当未配置环境变量时。当使用了2、3行时,也需要在创建浏览器对象时声明firefox_binary属性)
fp = webdriver.FirefoxProfile()
#获取Firefox配置文件对象
fp.set_preference("permissions.default.stylesheet",2)
#driver = webdriver.Firefox(firefox_binary=binary) #使用2、3行则需要声明firefox_binary属性
driver = webdriver.Firefox(firefox_binary=binary,firefox_profile=fp,capabilities=caps)#将另外修改的变量添加至Firefox浏览器对象中
driver.get("http://www.baidu.com/")
对于Ajax等异步加载技术,需要了解网页加载这些数据的过程称为逆向工程
#实例化session
session =request.session()
#使用session发送post,获取对方保存在本地的cookie
#注:此处用到的url为登录页url
url={url}
headers={}#构造请求头
post_data={}#构造账户登陆信息
session.post(url,headers=headers,data=post_url)
#在使用session后,请求登陆后的页面
url={}
response=session.get(url,headers=headers)
在爬取到网页后,需要使用网页解析器(用于解析网页的工具)从网页中解析和提取有用的信息,或者新的URL列表。Python对不同网页内容的解析有不同的方式:
(1) 针对文本的解析 :正则表达式
(2) 针对HTML/XML的解析 :XPath、BeautifulSoup、正则表达式
(3) 针对JSON的解析 :JSONPath
这几种技术的区别如下:
正则表达式 基于文本的特征来匹配或查找指定的数据,它可处理任何格式的字符串文档,类似于模糊匹配的效果。
XPath 和 BeautifulSoup 基于HTML/XML文档的层次结构来确定到达指定节点的路径,所以更适合处理层级明显的数据
JSONPath 专门用于JSON文档的数据解析
Python针对以上几种技术,提供了不同的模块或库来支持。下面通过比较几种支持的模块或库,对实际开发应用提供选择:
爬取工具 | 速度 | 支持技术 |
---|---|---|
re | 最快 | 正则表达式 |
lxml | 快 | XPath |
beautifulsoup4 | 慢 | BeautifulSoup |
lxml 库使用C语言编写,beautifulsoup4 库使用Python编写的,所以后者性能差一些。但是,beautifulsoup4 的API非常人性化,使用简单;lxml使用XPath语法书写麻烦,开发效率不如前者。另外,lxml 只局部遍历树结构(DOM),而 beautifulsoup4是载入整个文档,并转换成整个数结构。因此,beautifulsoup4 需要花费更多时间和内存,性能稍低于lxml
re(支持正则表达式)模块一般使用步骤:
(1) 使用compile() 函数将正则表达式以字符串形式编译为一个Pattern 类型的对象。
(2) 通过Pattern 对象提供的一系列方法对文本进行查找或替换,得到一个处理结果。
(3) 使用处理结果提供的属性和方法获取信息,如匹配到的字符串。
大多数情况下,从网站爬取的网页源码都会有汉字,如果要匹配这些汉字,就需要知道其对应的正则表达式,通常情况下,中文对应的Unicode编码范围为**[u4e00-u9fa5a]** 这个范围不包括全角(中文)标点,但大多数情况是可以使用的。
5.2.1 正则表达式常见字符及其含义
模式 | 描述 | 模式 | 描述 |
---|---|---|---|
. | 匹配除换行符外的任意字符 | \s | 匹配任何空白字符 |
* | 匹配前一个字符0次或多次 | \S | 匹配任何非空白字符 |
+ | 匹配前一个字符1次或多次 | \d | 匹配十进制数字,等价于[0-9] |
? | 匹配前一个字符0次或1次 | \D | 匹配非数字,等价于![^0-9](C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\正则表达式部分字符及其含义2 - 幕布.png) |
^ | 匹配字符开头 | \w | 匹配字母数字,等价于[A-Za-z] |
$ | 匹配字符末尾 | \W | 匹配非字母数字,等价于![^A-Za-z](C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\正则表达式部分字符及其含义1 - 幕布.png) |
() | 匹配括号内的表达式,也表示一个组 | [] | 用来表示一组字符 |
\ | 转义字符,跟在其后的一个字符警示区作为特殊原字符的含义 | | | 逻辑表达式 或 |
(?iLmsux) | 分组中可以设置模式,iLmsux中的每个字符代表一个模式,用法参见 | {n,m} | 匹配前一个元字符n至m次 |
(?Pcontent) | 分组匹配,取此分组中的内容时可以使用索引或name | (?:content) | 分组的不捕获模式,计算索引会跳过这个分组 |
(?P=name) | 分组的引用模式,可在同一个正则表达式用饮用前面命名过的正则表达式 | (?#content) | 注释,不影响正则表达式其他部分,用法参见 |
(?=content) | 前序匹配,表示所在位置右侧能匹配括号内的content | (?!=content) | 前序不匹配,表示所在位置右侧不匹配括号内的content |
(?<=content) | 后序匹配,表示所在位置左侧能匹配括号内的content | (?content) | 后序不匹配,表示所在位置左侧不要匹配括号内的content |
(?(id/name)yes|no) | 若前面指定id或name的分区匹配成功则执行yes处的正则,否则执行no处的正则 | \number | 匹配和前面索引为number的分组捕获到的内容一样的字符串 |
\A | 匹配字符串开始位置,忽略多行模式 | \Z | 匹配字符串结束位置,忽略多行模式 |
\b | 匹配位于单词开始或结束位置的空字符串 | \B | 匹配不位于单词开始或结束未知的空字符串 |
匹配模式常用在编译正则表达式时,作用涉及 区分大小写、忽略非法字符等。
正则表达式的模式时可以同时使用多个的,在Python里使用 | 同时添加多个模式
匹配模式 | 说明 |
---|---|
re.I | 忽略大小写 |
re.L | 字符集本地化,该功能是为了支持多语言版本的字符集使用环境。比如‘\w’在英文环境下,代表匹配字母数字,但在法语环境下,缺省设置导致不能匹配法语字符,加上该选项就可匹配。但该选项仍对中文不友好,不能匹配中文字符。 |
re.M | 多行模式,改变 ^ 和 $ 的行为 |
re.S | 点任意匹配模式,此模式下,‘.’的匹配不受限制,可匹配包括换行符在内的任何字符 |
re.X | 冗余模式(详细模式),此模式忽略正则表达式中的空白字符和#的注释(注释与表达式可多行书写) |
re.U | 使用\w,\W,\b,\B 这些元字符时将按照Unicode定义的属性 |
5.2.3 正则表达式常用函数
函数 | 用法详解 |
---|---|
re.compile(pattern,flags=0) | 该方法通常用作以下其他方法的基础。给定一个正则表达式pattern,默认flag = 0,表示不使用任何模式。 |
re.match(pattern,string,flags=0) | 从字符串起始位置进行匹配,匹配成功则返回匹配结果<_sre.SRE_Match object;span=(0,匹配成功的子串在字符串中的结束位置),match=‘[匹配的子串]’>否则返回None |
re.search(pattern,string,flags=0) | 扫描整个字符串返回第一个匹配成功的结果 |
re.findall(pattern,string,flags=0) | 扫描整个字符串,以列表的形式返回所有匹配成功的结果 |
re.split(pattern,string,maxsplit=0,flag=0) | 使用pattern寻找切分字符串的位置,返回包含切分后子串的列表,若匹配不到,则返回包含原字符串的一个列表。maxsplit 指定切分次数 |
re.sub(pattern,repl,string,count=0,flags=0) | 替换函数,将正则表达式pattern匹配到的子串替换为repl指定的字符串,count 用于指定最大替换次数 |
其他函数参考:https://www.cnblogs.com/dyfblog/p/5880728.html
方法 | 说明 |
---|---|
group([group1,group2….]) | 获得一个或多个分组截获的字符串;指定多个参数时将以元组的形式返回。group1可使用编号或者别名,默认返回group(0),即整个匹配的子串。没有截获字符串的组返回None |
groups([default]) | 以元组的形式返回全部分组接获的字符串。相当于调用group(1,2,…last),default表示没有接货字符串德祖以这个值替代,默认为None |
groupdict([default]) | 返回以有别名的组的别名为键,以该组截获的子串为值的字典,没有别名的组不包含在内。default含义同上 |
end([group]) | 返回指定的组截获的子串在string中的结束索引(子串最后一个字符的索引+1).group默认为0 |
start([group]) | 返回指定的组截获的子串在string中的起始索引(子串第一个字符的索引)。group默认为0 |
span([group]) | 返回(start(group),end(group)) |
expand(template) | 将匹配到的分组带入template中饭后返回。template可使用\id或\g、\g引用分组,但不能使用编号0.\id与\g等价,但\10将被认为是第10个分组,如果想表达\1之后是字符‘0’,只能使用\g<1>0 |
关于正则表达式更完整内容:https://www.cnblogs.com/dyfblog/p/5880728.html
正则表达式Python官方文档:https://docs.python.org/2/howto/regex.html#compiling-regular-expressions
正则表达式30分钟教程:https://www.jb51.net/tools/zhengze.html
XPath(XML Path Language) 即XML路径语言,用于确定XML树结构中某一部分的位置。(关于XML技术,可参照http://www.w3school.com.cn/xml/index.asp 进行学习)XPath技术基于XML树结构,使用路径表达式选取XML文档中的节点或节点集,从而能够在树结构中遍历节点(元素/属性等)
XPath使用路径表达式在文档中进行导航.这个表达式是从某个节点开始,之后顺着文档树结构的节点进一步查找.由于查询路径的多样性,可以将XPath的语法按照如下情况进行划分:
节点是沿着路径选取的,既可从根节点开始,也可从任意位置开始.以下是XPath中用于选取节点的表达式:
表达式 | 说明 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选取文档中的节点,而不考虑其位置 |
. | 选取当前节点 |
… | 选取当前节点的父节点 |
@ | 选取属性 |
text() | 获取文本 |
标签/@属性 | 获取标签内部某属性值 |
//text() | 获取标签下所有文本,也可用于其他获取所有相同标签下的数据 |
以下是一些选取节点的表达式的举例:
bookstore
bookstore/book
//book
bookstore//book
//@lang
谓语指的是路径表达式的附加条件,这些条件都写在方括号中,表示对节点进行进一步筛选,用于查找某个特定节点或者包含某个指定值的节点.具体格式如下:
元素[表达式]
以下是部分常用的带谓语的路径表达式,以及对这些表达式功能的说明:
表达式 | 说明 |
---|---|
/bookstore/book[1] | 选取属于bookstore子元素的第一个book元素 |
/bookstore/book[last()] | 选取属于bookstore子元素的最后一个book元素 |
/bookstore/book[last()-1] | 选取属于bookstore子元素的倒数第二个book元素 |
/bookstore/book[position()❤️] | 选取最前面的两个属于bookstore元素的子元素的book元素 |
//title[@lang] | 选取所有的title元素,且这些元素拥有名称为lang的属性 |
//title[@lang=‘eng’] | 选取所有title元素,且这些元素拥有值为eng的lang元素 |
/bookstore/book[price>35.0] | 选取bookstore元素的所有book元素,且其中的price元素的值须大于35.0 |
/bookstore/book[price>35]/title | 选取bookstore元素中的book元素的所有title元素,且book元素中的price元素的值须大于35.0 |
XPath可以使用通配符(*****)来选取未知的节点.下面列举带有通配符的表达式.
通配符 | 说明 |
---|---|
* | 匹配任何元素节点 |
@* | 匹配任何属性节点 |
node() | 匹配任何类型的节点 |
以下是一些使用通配符的示例:
/bookstore/*
//*
//title[@*]
在路径表达式中使用“|” 运算符,以选取若干路径.以下是部分示例:
//book/title | //book/price
//title | //price
/bookstore/book/title | //price
lxml是使用Python语言编写的库,主要用于解析和提取HTML和XML格式的数据。利用XPath语法快速定位特定的元素或节点。
lxml库大部分功能都位于lxml.etree模块中,导入该模块的常见方法如下:
from lxml import etree
lxml库的一些相关类如下:
类 | 说明 |
---|---|
Element类 | 可理解为XML的节点 |
ElementTree类 | 可理解为一个完整的XML文档树 |
ElementPath类 | 可理解为XPath,用于搜索和定位节点 |
Element 类是XML处理的核心类,可直观地理解为XML的节点,大部分XML节点的处理围绕着Element类进行的。要想创建一个节点对象,则可通过构造函数直接创建。例如:
root = etree.Element('root')#参数root表示节点的名称
关于Element类的相关操作,主要分为三个部分,分别为: 节点操作、节点属性的操作、节点内文本的操作。
#(1) 节点操作:若要获取节点名称,可通过tag属性获取.例如:
print(root.tag)
#(2) 节点属性的操作:在创建节点的同时,可为节点增加属性。节点中的属性是以类似字典的key-value的形式来存储。通过构造方法创建节点是,可以在该方法中以参数的形式设置属性,其中参数的名称表示属性的名称,参数的值表示属性的值。示例如下:
root = etree.Element('root',interesting='totally')
print(etree.toString(root))#toString()函数将元素序列化为XML树的编码字符串表示形式,输出
#通过set()方法可给已有的节点添加属性。调用该方法可以传入两个参数,第一个参数表示属性名称,第二个表示属性的值。例如:
root.set('age','30')
print(etree.toString(root))#toString()函数将元素序列化为XML树的编码字符串表示形式,输出
#(3) 节点内文本的操作:一般情况下,可通过text、tail属性或者xpath()方法来访问文本内容。通过text属性访问节点的示例如下:
root = etree.Element('root') #创建root节点
root.text = 'Hello World' #给root节点添加文本
为能够将XML文件解析为树结构,etree模块提供了如下3个函数:
函数 | 说明 |
---|---|
fromstring() | 从字符串中解析XML文档或片段,返回根节点(或解析器目标返回的结果) |
XML() | 从字符串常量中解析XML文档或片段,返回根节点(或解析器目标返回的结果) |
HTML() | 从字符串常量中解析HTML文档或片段,返回根节点(或解析器目标返回的结果) |
其中,XML 函数的行为类似于 fromstring() 函数,通常用于将XML自变量直接写入到源代码中;HTML()函数可以自动补全缺少的和标签.以下为三个函数的示例:
xml_data = 'data '
#fromstring() 函数
root_one = etree.fromstring(xml_data)
print(root_one.tag)
#结果: root
print(etree.toStrintg(root_one))
#结果: b'data '
#XML() 函数,与fromstring() 基本一致
root_two = etree.XML(xml_data)
print(root_two.tag)
#结果: root
print(etree.toString(root_two))
#结果: b'data '
#HTML() 函数,如果没有和标签,会自动补上
root_three = etree.HTML(xml_data)
print(root_three.tag)
#结果: html
print(etree.toString(root_three))
#结果: b'data '
除以上三种函数外,还可调用parse() 函数从XML文件中直接解析。在调用函数时,如果没提供解析器,则使用默认默认解析器,函数返回一个ElementTree类的对象.例如:
html = etree.parse('./hello.html')
result = etree.toString(html,pretty_print = True)
**补 : ** 关于解析器,后面BeautifulSoup库会讲到,勿躁。
ElementTree 类中附带了一个类似于XPath路径语言的ElementTree类.在ElementTree类或Elements类的API文档中,提供了三个常用的方法,可满足大部分搜索和查询需求,并且这3个方法的参数都是XPath语句.具体如下:
方法 | 说明 |
---|---|
find() | 返回匹配到的第一个子元素 |
findall() | 以列表的形式返回所有匹配的子元素 |
iterfind() | 返回一个所有匹配元素的迭代器 |
示例如下:
#从文档树根节点开始,搜索符合要求的节点.
#从字符串中解析XML,返回根节点
root = etree.XML('aText ')
#从根节点查找,返回匹配到的节点名称
print(root.find('a').tag)
#从根节点开始,返回匹配到的第一个节点名称
print(root.findall("./a[@x]")[0].tag)
对于HTML文档,使用lxml库中的路径表达式技巧,通过调用xpath()方法可匹配选取的节点.(xpath语法与lxml库的结合)
目前,BeautifulSoup 已经停止开发,官网推荐现在的项目使用beautifulsoup4(Beautiful Soup4 版本,简称 bs4) 开发
bs4 是一个HTML/XML的解析器,其主要功能是解析和提取HTML/XML数据.它支持CSS选择器、Python标准库中的HTML解析器、lxml的XML解析器。通过使用这些转化器,实现了惯用文档导航和查找方式,节省时间,提高效率。
bs4 库会将HTML文档转化成树结构(HTML DOM),该结构中的每个节点都是一个Python对象。这些对象可归纳为以下四类:
类 | 说明 |
---|---|
bs4.element.Tag | 表示HTML中的标签,是最基本的信息组织单元,它有表示标签名字的name属性 、表示标签属性的attrs属性两个重要属性 |
bs4.element.NavigableString | 表示HTML中的标签的文本(非属性字符串) |
bs4.BeautifulSoup | 表示HTML DOM中的全部内容,支持遍历文档树和搜索文档树的大部分方法 |
bs4.element.Comment | 表示标签内字符串的注释部分,是一种特殊的String对象 |
使用bs4的一般流程如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-p0AvqUl8-1575726189585)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\bs4库的使用流程.jpg)]
通过一个字符串或者类文件对象(存储在本地的文件句柄或Web网页句柄)可以创建BeautifulSoup类的对象
BeautifulSoup类中构造方法的语法如下:
def __init__(self,markup="",feature=None,builder=None,parse_only=None,from_encoding=None,exclude_encoding=None,**kwargs)
上述方法的一些参数含义如下:
参数 | 说明 |
---|---|
markup | 解析的文档字符串或文件对象 |
features | 解析器的名称 |
builder | 指定的解析器 |
from_encoding | 制定的编码格式 |
exclude_encodings | 排出的编码格式 |
示例如下:
#根据字符串html_doc 创建一个BeautifulSoup对象
from bs4 import BeautifulSoup
soup = BeautifulSoup(html_doc,'lxml')
#两个参数中,第一个表示包含被解析HTML文档的字符串,第二个表示使用lxml解析器进行解析。创建Beautifulsoup对象常用以上这两个参数
目前,bs4支持的解析器包含Python标准库、lxml和html5lib。以下列举它们的使用方法和缺点。
解析器 | 使用方法 | 优势 | 劣势 |
---|---|---|---|
Python标准库 | BeautifulSoup(markup,“html.parser”) | Python内置库;执行速度适中;文档容错能力强 | 在Python 2.7.3或3.2.2之前的版本中文档容错能力差 |
lxml HTML解析器 | BeautifulSoup(markup,“lxml”) | 速度快;文档容错能力强 | 需要安装C语言库 |
lxml XML解析器 | BeautifulSoup(markup,“xml”) | 速度快;唯一支持XML的解析器 | |
html5lib | BeautifulSoup(markup,“html5lib”) | 最好的容错性;以浏览器的方式解析文档;生成HTML5格式的文档 | 速度慢;不依赖外部扩展 |
注 : 在创建BeautifulSoup对象时,如果没明确地指定解析器,BeautifulSoup对象会根据当前系统安装的库自动选择解析器。解析器的选择顺序: lxml、html5lib、Python标准库。当已明确解析的文档类型或者指定解析器的情况下,选择解析器的优先顺序会发生变化。
通常BeautifulSoup解析得到的Beautifulsoup对象格式较杂乱,可在输出时使用 **prettify() **方法对BeautifulSoup对象进行美化(为标签和内容添加换行符和缩进,以便于更友好直观地显示HTML内容),示例如下:
print(soup.prettify())
BeautifulSoup提取对象的方法可归纳为三种方式:
(1) 遍历文档树
(2) 搜索文档树
(3) CSS选择器
遍历文档树类似爬树,从标签的下一层标签开始,按照标签顺序一个一个地深入,寻找到目标标签。多个示例如下:
soup.header.h3 #获取标签
soup.header.div.contents #使用contents将某一标签的子标签以列表的形式输出
soup.header.div.contents #输出某一标签的奇数项子标签
for child in soup.header.div.cildren:
print(child) #使用children方法获得所有子标签
for child in soup.header.div.descendants:
print(child) #使用descendants方法获得所有后代(子孙)标签
a_tag = soup.header.a
a_tag.parent #使用parent方法获得父节点的内容
其他具体方法和例子参考章末官方文档
网页中的信息都存在于网页的文本或者各种不同标签的属性值,为获得这些有用的网页信息,可通过一些查找方法获取文本或标签属性。bs4 库内置一些查找方法,其中最常用的两个方法功能如下:
方法 | 功能 |
---|---|
find() | 查找符合查询条件的第一个标签节点 |
find_all() | 查找所有符合查询条件的标签节点,并返回一个列表 |
这两个方法的参数相同,以find_all() 为例,介绍该方法中参数的作用,find_all() 定义如下:
find_all(self,name=Nobe,attrs={key:value},recursive=True,text=None,limit=None,**kwargs)
上述方法中的一些重要参数含义如下:
查找所有名字为name的标签,但**‘name=’**字符串会被忽略.以下是name参数的几种情况:
在搜索的方法中传入一个字符串,BeautifulSoup 对象会查找与字符串完全匹配的内容.例如:
soup.find_all('b') #查找文档中所有的标签
如果传入一个正则表达式,那么BeautifulSoup对象会通过re模块的match()函数进行匹配.例如:
soup.find_all(re.compile("^b"))#使用正则表达式匹配所有以字母b开头的标签
如果传入一个列表,那么BeautifulSoup对象将会与列表中任意元素匹配的内容返回.例如:
soup.find_all(["a","b"]) #返回文档中所有标签和标签
如果某个指定名字的参数不是搜索方法中内置的参数名,那么在进行搜索时,会把该参数当作指定名字的标签中的属性搜索.例如:
soup.find_all(id='link2')#在find_all()方法中传入名称为id,值为link2的参数,此时BeautifulSoup对象会搜索每个标签的属性
若传入多个指定名字的参数,则可同时过滤出标签中的多个属性.例如:
import re
soup.find_all(href=re.compile("elsie"),id='link1')
若传入的参数属于Python保留字(如: class),可以在保留字后添加一条下划短线.例如:
soup.find_all(class_='sister')
有些标签的属性名不能使用(例如:HTML5的“data-”属性,在程序中使用会出现SyntaxError异常信息),可通过find_all()方法的attrs参数传入传入一个字典来搜索包含特殊属性的标签.例如:
#错误示例
soup.find_all(data-foo='value')
#异常信息如下:
#SyntaxError:keyword can't be an expression
#正确示例
soup.find_all({"data-foo":'value'})
#程序正常输出包含标签内容的列表
通过在find_all() 方法中传入text参数,可搜索文档中的字符串内容.与name参数可选值一样,text参数也可以接受字符串、正则表达式、列表.例如:
soup.find_all(text="Elsie")
#输出文本内容: [u'Elsie']
soup.find_all(text={"Tillie","Elsie","Lacie"})
#输出文本内容:[u'Elsie',u'Lacie',u'Tillie']
若DOM树非常大,且不需要获取全部结果,可使用limit参数限制返回结果的数量.其效果类似于SQL语句中的limit关键字产生的效果,一旦搜索结果达到limit的限制,将停止搜索.例如:
soup.find_all("a",limit=2)#搜索标签,最多获取两个符合条件的结果
再调用find_all() 方法时,BeautifulSoup对象会检索当前节点的所有子节点。此时,如果只想搜索当前节点的直接子节点,就可使参数recursive=False.例如:
soup.find_all("title",recursive=False)
#若存在直接子节点,则返回结果为直接子节点,否则返回空列表
为使用CSS选择器大达到筛选节点的目的,在bs4库的BeautifulSoup类中提供select()方法,该方法会将搜索到的结果放到列表里
CSS选择器的查找方法可分为如下几种:
在编写CSS时,标签的名称不用加任何修饰。调用select() 方法时,可以传入包含某个标签的字符串。
使用CSS选择器查找标签的示例如下:
soup.select('title')#使用select() 方法查找标签title及其内容
#查找结果可能为:[content ]
在编写CSS时,查找包含class的标签,需要在类名前加 ‘.’ .例如:
soup.select('.sister') #查找类名为sisiter的标签
#查找结果:[content]
查找包含ID的标签,在编写CSS时需要在ID名前加**"#"**.例如:
soup.select('#sister') #查找ID名为sister的标签
#查找结果: [content]
组合查找与编写CLASS文件时标签名/类名/id名的组合原理一样,二者需要空格分开.例如:
soup.select('p #link1') #在标签中查找id=link1的内容
#可能的结果: [content]
可使用**“>”**将标签与子标签分隔,从而找到某个标签下的直接子标签.例如:
soup.select('head > title')
#可能的结果:[content ]
当通过属性元素进行查找时,属性需要用中括号括起来。但是,属性和标签属于同一节点,中间不能加空格,否则无法匹配到。例如:
soup.select('a[href='[webpage]']')#查找含有属性href的标签
#可能的结果: [content]
同样,属性仍可与上述查找方式组合,即不在同一节点的属性使用空格隔开,同一节点的属性之间不加空格.例如:
soup.select('p a[href='[webpage]']') #查找标签和含有属性href的标签
#结果:返回列表
通过select方法返回的结果都是列表形式,可通过遍历形式输出,然后使用**get_text()**方法获取内容
soup.select('p')[0].get_text()
#获取标签内的内容,例如 src='http://www.baidu.com'
#需要使用get('src')方法
#该方法与以上方法在搜索文档树中也可使用
BeautifulSoup4.4.0官方文档:https://docs.pythontab.com/beautifulsoup4/
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,可使人们很容易地进行阅读和编写,同时方便机器解析与生成。其适用于进行数据交换的场景,如 网站前台与后台之间的数据交互。
JSON 比 XML 更简洁,其采用完全独立于文本语言的文本格式来存储和表示数据。语法规则如下:
(1) 使用**键值对(key:value)**标识对象属性和值
(2) 使用逗号**(,)**分隔多条数据
(3) 使用花括号**{}** 包含对象
(4) 使用方括号**[]** 表示数组
在JavaScript语言中,一切皆对象,所以任何支持的类型都可通过JSON表示,如:字符串、数字、对象、数组等。其中,对象和数组是比较特殊且常用的两种类型。
JSON键/值对的格式 :Field_Name(包含在双引号中) : value
#示例:
"name" : "Xiaoming"
可能的值:
值 | 说明 |
---|---|
Number | 数字(整数或浮点数) |
String | 字符串(在双引号中) |
Boole | 逻辑值(true 或 false) |
Array | 数组(在方括号中) |
Object | 对象(在花括号中) |
Null | 空值 |
对象在Javascript中表示为花括号括起来的内容,数据结构为{key1:value,key2:value2,……}的键值对结构.在面向对象的语言中,key为对象的属性,value为对象的属性值,所以获取属性值的方法为**“对象.key”**,该属性类型可以是数字、字符串、数组、对象。在Web应用中,将最顶层的节点定义为对象是一种标准做法。
#示例:一个对象
{"name":"xiaohuang","age":18}
数组在Javascript中时中括号[]括起来的内容,数组结构:[Field1,Filed2,Field3…] Filed(字段)值的类型可以是 数字、字符串、数组、对象。取值方式使用索引获取。
#示例:一个编程语言的数组
["Python","Javascript","C++"......]
JSON与XML都是文本格式语言,他们经常用于数据交换和网络传输.以下从几个方面进行比较:
JSON与XML都有很好的扩展性,但JSON与Javascript语言结合更紧密,在Javascript语言中使用JSON可谓无缝连接
JSON与XML的可读性难分优劣,前者是简洁的语法,后者是规范的标签形式.
在不适用工具的情况下,XML文档相比JSON文档,需要的字符量更多
JSON与XML都是可扩展的结构,若不知文档结构,解析文档则非常不方便。所以最好在知道文档结构的情况下进行解析。但真实开发情况下,看到文档的字符串,就可知其结构。
由于省却大量标签,JSON的有效数据率比XML高很多。且其语法格式简单,层次结构清晰,比XML更易阅读。另外,由于占用的字符量少,用于网络传输时,能节约带宽,提高传输效率。
json模块提供Python对象的序列化和反序列化的功能。其中:
功能 | 说明 |
---|---|
序列化 | 将一个Python对象编码为JSON字符串的过程 |
反序列化 | 将JSON字符串解码转换为Python对象的过程 |
json模块提供4种方法:dumps()、dump()、loads()、load(),用于字符串和Python数据类型间进行转换。其中loads()和load()用于Python对象的反序列化,dumps()和 dump() 方法用于Python对象的序列化
把JSON格式字符串解码转换成Python对象。从JSON类型向Python类型转化的对照如图:
JOSN | Python |
---|---|
object | dict |
array | list |
string | unicode |
number(int) | int,long |
number(real) | float |
true | True |
false | False |
null | None |
#示例:loads()方法的应用:
import json
str_list = '[1,2,3,4]'
str_dict = '{"city":"北京","name":"小明"}'
json.loads(str_list)
#结果: [1,2,3,4]
json.loads(str_dict)
#结果: {'city':'北京','name':'小明'}
读取文件中的JSON形式的字符串,转化成Python类型。它与json.loads() 方法的区别在于:json.load() 读取的是文件,json.loads() 读取的是字符串
import json
print(json.load(open("test.json")))
#输出结果:[{'city':'\u5317\u4eac'},{'name':'\uu5c0f\u660e'}]
#产生原因:打开文件默认编码为Unicode,需要在open函数中特殊设置编码格式
print(json.load(open("test.json",encoding="utf-8")))
将Python类型编码为JSON字符串,返回一个str对象。Python原始类型向JSON类型转化对照如图:
Python | JSON |
---|---|
dict | object |
list,tuple | array |
str,unicode | string |
int,long,float | number |
Ture | true |
False | false |
None | null |
#示例:dumps方法的使用
demo_list = [1,2,3,4]
print(json.dumps(demo_list))
#输出结果:[1,2,3,4]
demo_tuple = (1,2,3,4)
print(json.dumps(demo_tuple))
#输出结果:[1,2,3,4]
demo_dict={"city":"北京","name":"小明"}
print(json.dumps(demo_dict))
#输出结果:{"city":"\u5317\u4eac","name":"\u5c0f\u660e"}
#原因:json.dumps()处理中文默认使用ASCII编码,会导致中文无法显示
#解决办法: 处理中文时,添加参数ensure_ascii=False来禁用ASCII编码
print(json.dumps(demo_dict,ensure_ascii=False))
#输出结果: {"city":"北京","name":"小明"}
将Python内置类型序列化为json对象后写入文件。json.dump()方法与json.dumps() 方法的区别在于json.dump()写入的是文件,json.dumps()写入的是字符串。
#示例: dump() 方法的使用
import json
str_list = [{"city":"北京"},{"name":"小明"}]
json.dump(str_list,open("listStr.json","w"),ensure_ascii = False)
str_dict={"city":"北京","name":"小明"}
json.dump(str_dict,open("listStr.json","w"),ensure_ascii = False)
JSONPath是一种信息提取类库,是从JSON文档中抽取指定信息的工具。其支持的语言有多种,包括: JavaScript、Python、PHP、java
JSONPath的语法与Xpath类似,下图所示JSONPath与Xpath语法对比
XPath | JSONPath | 描述 |
---|---|---|
/ | $ | 根节点 |
. | @ | 现行节点 |
/ | . or [] | 取子节点 |
… | n/a | 取父节点,JSONPath未支持 |
// | … | 不管位置,选择所有符合条件的节点 |
* | * | 匹配所有元素节点 |
@ | n/a | 根据属性访问,JSONPath不支持。因为JSON是key-value递归结构,不需要属性访问 |
| | [.] | 支持迭代器中做多选 |
[] | ?() | 支持过滤操作 |
n/a | () | 支持表达式计算 |
() | n/a | 分组,JSONPath不支持 |
下面使用JSON文档演示JSONPath的具体使用。文档节选内容如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cXltdiFO-1575726189586)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\JSON文件节选.png)]
假设变量bookJson中已包含这段JSON字符串,可通过反序列化得到Python对象:
city = json.loads(bookJson)
checkurl='$.results[0].name'
print(jsonpath.jsonpath(city,checkurl))
#输出:[‘后海公园’]
checkurl = "$.results[*]"
object_list = jsonpath.jsonpath(books,checkurl)
print(object_list)
checkurl = "$.results[0]"
object = jsonpath.jsonpath(books,checkurl)
print(object)
checkurl = "$.results[*].title"
object = jsonpath.jsonpath(books,checkurl)
print(object)
checkurl = "$.results[?(@.name=="后海公园")]"
object = jsonpath.jsonpath(books,checkurl)
print(object)
checkurl = "$.results[?(@.lat>38)]"
object = jsonpath.jsonpath(books,checkurl)
print(object)
checkurl = "$.results[?(@.lat)]"
object = jsonpath.jsonpath(books,checkurl)
print(object)
注:若获取的结果为空,将输出False
部分链接使用get方法返回的字符串包含JSON字符串,可以对链接进行相应去除处理,获得JSON字符串(而不是使用正则表达式提取JSON字符串)
例如:(易车网途观白色外观图)
http://webapi.photo.bitauto.com/photoApi/capi/pc/v1/photo/GetListWithAlbum?callback=jQuery19104261347313637116_1561127957333&modelId=2871&pageIndex=1&pageSize=30&albumtype=1&OnSale=True&CarYear=&groupid=6&colorid=10684
返回的结果为:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-o0tVOOXK-1575726189587)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\JSON补充图1.png)]
可以看到在JSON字符串开始前,有一段JQuery字样的内容,去除链接内的Query内容,如下:
http://webapi.photo.bitauto.com/photoApi/capi/pc/v1/photo/GetListWithAlbum?modelId=2871&pageIndex=1&pageSize=30&albumtype=1&OnSale=True&CarYear=&groupid=6&colorid=10684
返回的结果如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QtCIP51w-1575726189587)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\JSON补充图2.png)]
DOM把整个XML读入内存,解析为树,因此占用内存大,解析慢,优点是可以任意遍历树的节点
SAX流模式边读边解析,占用内存小,解析快,缺点是需要自己处理事件
详细信息请百度
#存储示例
title = "This is a text sentence."
with open(r'test.txt','a+',encoding="utf-8") as f:#以附加写入的模式,utf-8的编码打开处理文件
f.write(title)
f.close()#操作完成,关闭文件
几种文件打开方式:
读写方式 | 可否读写 | 若文件不存在 | 写入方式 |
---|---|---|---|
W | 写入 | 创建 | 覆盖写入 |
w+ | 读取+写入 | 创建 | 覆盖写入 |
R | 读取 | 报错 | 不可写入 |
r+ | 读取+写入 | 报错 | 覆盖写入 |
A | 写入 | 创建 | 附加写入 |
a+ | 读取+写入 | 创建 | 附加写入 |
几个方法及其作用 :
方法 | 作用 |
---|---|
read([size]) | 读取指定大小的内容,size未指定读取回整个文件 |
readline() | 读取一行 |
readlines() | 读取整个文件,返回以行为元素的列表 |
CSV是以逗号分隔数值的文件格式,其文件以纯文本的形式存储表格数据。CSV文件每行以换行符分隔,每列以逗号分隔。通常以第一行为列名。相比Excel文件,CSV文件不能嵌入图像图表
import csv #Python内置csv模块,用于处理csv文件
with open('[filename].csv',[mode],encoding='UTF-8',newline='') as f:#newline若不指定,则每写入一行,酱油一空行被写入
csv_reader = csv.read(f)#返回一个迭代器,它只能使用next()和for循环
reader.next()#返回下一行内容
import csv
data = ['1','2','3','4']
with open([csvfile],[mode],encoding='UTF-8') as f:
w = csv.writer(f) #创建writer对象
w.writerow(data)#逐行写入数据,若相同形式的数据有多行,需要用for循环逐行写入
MySQL是一种关系型数据库管理系统,所使用的是SQL语言,是访问数据库最常用的标准化语言。
由于MySQL关系型数据库体积小且速度快,因此成为在网络爬虫数据存储中常用的数据库。
操作格式 | 说明 |
---|---|
INSERT INTO table_name(column1,column2,…) VALUES(value1,value2,value3,….) | 插入数据(注意:column与value数量与位置对应) |
SELECT column_name1,column_name2 FROM table_name WHERE conditions | 读取 |
UPDATE table_name SET column1 = value1,column2 = value2 WEHRE conditions | 更改 |
DELETE FROM table_name WHERE conditions | 删除 |
操作格式 | 说明 |
---|---|
PRAGMA table_info(table_name) | 获取表结果 |
CREATE DATABASE database_name | 创建库 |
CREATE TABLE table_name(‘[coloum_name] [type and size]’) | 创建表(**注 :**需要注意同时设置表结构,包括表头、主键等) |
操作 | 说明 |
---|---|
conn = pymysql.connect(host=“localhost”,user=“root”,passwd=“[数据库登陆密码]”,db=“[连接的数据库名称]”,charset=“[数据库的字符集]”) | 连接数据库(**注 :**字符集的设置防止乱码) |
cur = conn.cursor() | 获取游标 |
cursor.execute(sql_str)/cursor.executemany(sql_str) | CRUD操作(单条/批量) |
conn.commit() conn.close() | 提交和关闭操作 |
含义 : 查询记录将多个表中的记录连接(join)并返回结果
连接方式 :
书写格式 | 说明 |
---|---|
SELECT |
交叉连接(cross join),返回两表的笛卡尔乘积 |
SELECT |
内连接(cross join),返回两表的交集 |
SELECT |
左连接(join(A,B)),返回表A的所有记录,另外表B中匹配的记录有值,没匹配的记录返回null |
SELECT |
右连接(join(A,B)),返回表B的所有记录,另外表A中匹配的记录有值,没匹配的记录返回null |
NoSQL泛指非关系型数据库,传统的SQL数据库把数据分隔到每个表中,并用关系连接起来。但在大数据量、高并发环境下,SQL数据库扩展性差,写入压力大,表结构更改困难。相比之下,NoSQL易扩展,数据之间无关系,具有极高的读写性能。
MySQL是使用SQL语言访问数据库的,该数据库的基本组成单元是数据表。而MongoDB是一种非关系型数据库,其数据库的基本组成单元是集合。下面列出MongoDB的一些常见术语,并与MySQL中的术语比较,以进一步了解MongoDB的结构
SQL术语/概念 | MongoDB术语/概念 | 解释/说明 |
---|---|---|
database | database | 数据库 |
table | collection | 数据库表/集合 |
row | document | 数据记录行/文档 |
index | index | 索引 |
table joins | 多表连接,MongoDB不支持 | |
primary key | primary key | 主键,MongoDB自动将_id字段设为主键_ |
在MongoDB中最基本的概念和组成元素是数据库、集合和文档。下面介绍三者的作用
数据库(DataBase)表示一个集合的容器。通常情况下,MongoDB可创建多个数据库,默认的数据库为db,储存在data目录中。以下是MongoDB的部分操作及其解释
操作 | 说明 |
---|---|
show dbs | 显示所有数据库列表 |
db | 显示当前数据库对象或者集合 |
use | 连接到指定的数据库 |
文档(Document)是一组由键/值对组成的对象,对应着关系型数据库的行。MongoDB的文档不需设置相同的字段,并且相同的字段不需要相同的数据类型,这与关系型数据库有很大区别,也是MongoDB非常突出的特点。
#示例如下:
{"name":"Jane","age":30}
'''
注意:
文档中键/值对有顺序,文档中的值可以为字符串,也可为其它数据类型。
每个文档中都有_id属性,保证文档的唯一性。在插入文档时,可自行设置_id属性,若没提供,则MongoDB会为每个文档设置一个独特的_id,类型ObjectID
ObjectID是一个12字节的十六进制数。其中,前4字节表示当前的时间戳,随后3字节的机器ID,接着2字节的服务进程ID,最后3字节是简单增量值
'''
集合(Collection)是一组文档,类似于关系型数据库的表,它没有固定的结构,所以可以往集合中插入不同格式和类型的数据。
Pymongo是用于MongoDB的开发工具,是Python操作MongoDB数据库的推荐方式
PyMongo中主要提供如下类与MongoDB数据库进行交互:
类 | 说明 |
---|---|
MongoClient | 用于与MongoDB服务器建立连接 |
DataBase | 表示MongoDB中的数据库 |
Collection | 表示MongoDB中的集合 |
Cursor | 表示查询方法返回的结果,用于对多行数据进行遍历 |
PyMongo库的基本使用流程如下:
(1) 创建一个MongoClient类的对象,与MongoDB服务器建立连接
(2) 通过MongoClient对象访问数据库(DataBase对象)
(3) 使用上一步骤的数据库建立集合(Collection对象)
(4) 调用集合中提供的方法在集合中插入、删除。修改和查询文档
1.创建连接
构造方法及其参数含义如下:
class pymongo.mongo_client.MongoClient(host='localhost',port='27017',document_class=dict,tz_aware=False,connect=True,**kwargs)
#上述方法包含的参数含义如下:
#host:表示主机名或IP地址
#port:表示连接的端口号
#document_class:从此客户端查询返回的文档默认使用此类
#tz_aware:若为True,则此MongoClient作为文档中的值返回的datetime实例,将被时区所识别
#connect:若为True(默认),则立即开始在后台连接到MongoDB,否则连接到第一个操作。
建立连接实例如下:
#形式 1:不传入任何参数,连接到默认主机(localhost)和端口(27017)
client = MongoClient()
#形式 2:显示指定主机
client = MongoClent('localhost',27017)
#形式 3:使用MongoDB的URL路径形式传入参数
client = MongoClient('mongodb://localhost:27017')
2.访问数据库
只要建立与Mongo服务器的连接,就可直接访问任何数据库。访问方式可将其当做属性一样,使用点语法访问或者使用字典的形式访问。
#点语法访问
db = client.pymongo_test
#字典的形式访问
db = client['pymongo_test']
注意: 若指定的数据库已存在,则直接访问此数据库;如果指定的数据库不存在,就会自动创建一个数据库。
3.创建集合
创建集合的方式与创建数据库类似,通过数据库使用点语法的形式进行访问。语法格式及示例如下:
#数据库名称.集合名称
column = db.student #访问db数据库中的student集合
4.插入文档
在集合中插入文档的方式主要有两种:
(1) insert_one() 方法 : 插入一条文档对象
(2) insert_many() 方法: 插入列表形式的多条文档对象
示例如下:
#示例: 插入一条文档
try:
client = MongoClient(host="localhost",27017)
db = client.mongo_test
collection = db.student
result = collection.insert_one({'name':'ceshi','age':20})
print(result)
exception Exception as error:
print(error)
5. 查询文档
用于查找文档的方法主要有如下几个:
(1) find_one() 方法:查找一条文档对象
(2) find_many() 方法: 查找多条文档对象
(3) find() 方法: 查找所有文档对象
示例如下:
#以find()方法,介绍如何查找集合中的所有文档
try:
client = MongoClient()
db = client.mongo_insert()
collection = db.student
result = collection.find({'age':20}) #根据条件{'age':20}查找文档
print(result)
exception Exception as error:
print(error)
6. 更新文档
用于更新文档的方法主要有以下几个:
(1) update_one() 方法: 更新一条文档对象
(2) update_many() 方法: 更新多条文档对象
示例如下:
#示例 1 :更新一条文档
collection.update_one({'age':22,{$'set':{'name':'zhaoliu'}}})
#示例 2: 更新多条文档
collection.update_many({'age':22,{$'set':{'name':'zhaoliu'}}})
7.删除文档
用于删除文档的方法如下:
(1) delete_one() 方法: 删除一条文档对象
(2) delete_many() 方法: 删除所有记录
示例如下:
#示例: 删除集合中的所有文档
collection.delete_many({})
条件操作符用于比较两个表达式并从mongoDB集合中获取数据。
MongoDB中条件操作符如下:
操作符 | 说明 |
---|---|
$gt | 大于(>) |
$lt | 小于(<) |
$gte | 大于等于(>=) |
$lte | 小于等于(<=) |
$ne | 不等于(!=) |
$eq | 等于(=) |
使用示例:
#条件操作符用于查询文档设置查询条件
#查询'col'集合中'like'大于100的文档数据
data = col.find({like:{$gt:100}})
$type操作符是基于BSON类型来检索结合中匹配的数据类型,并返回结果.
使用示例如下:
#获取'col'集合中title为String的数据
col.find({"title":{$type:'string'}})
Limit方法用于读取指定数量的文档
Skip方法用于跳过指定数量的文档
使用示例如下:
#基本语法如下:
#Limit()方法
db.Collection_name.find([conditions]).limit(number)
#Skip() 方法
db.Collection_name.find([conditions]).skip(number)
对于文档的更新,针对某个或多个文档秩序部分更新可使用原子操作进行更新。原子操作是种特殊的键,用于指定复杂的操作。如:增加/删除/调整,甚至操作数组或内嵌文档。
指定一个键并更新键值,若键不存在则创建。
#示例如下:
#更新size键值
col.update({$set:{"size":10}})
删除一个键
#删除filed键
col.update({$unset:{"field":1}})
把value追加到filed里,filed必须为数组类型,若filed不存在,会在新增一个数组类型.
col.update({$push:{field:value}})
另外,$pushAll 类似,但只是一次可追加多个知道数组字段内
col.update({$pushAll:{field:value_array}})
对文档中某个值为数字型的键进行增删操作
col.update({$inc:{field:value}})
增加一个值到数组内,且只有当这个值不在数组才增加
删除数组的第一个或最后一个元素
col.update({$pop:{field:1}})#删除数组中第一个元素
修改字段名称
col.update({$rename:{old_filed_name:new_field_name}})
从数组filed内删除一个等于value值的字段
col.update({$pull:{filed:value}})
其他相关内容参考网络
反爬虫的原因:
(1) 由于爬虫不算是真正用户的流量,导致网站浏览量激增,浪费网站流量,也就是浪费钱。
(2) 数据是每家公司的宝贵资源。大数据时代下,数据的价值显现。若竞争对手拿到相关数据,并加以利用,长此以往会导致公司竞争力下降。
反爬虫的方式:
**(1)**不返回网页,如: 不返回内容和延迟网页返回时间
(2) 返回数据非目标网页,如返回错误网页、空白页和多页爬取仅返回一页
(3) 增加爬取的难度,如: 登陆才可查看和登陆需填写验证码
应对反爬虫的中心思想是让爬虫看起来更像正常用户的浏览行为。
将headers改为真正浏览器的格式,包括对User-Agent、Host、Referer等的设置。
使用time和random库在爬虫访问之间设置随机的时间间隔,模拟真实随机访问的情况。示例如下:
import random,time
sleep_time = random.randint(0,2) + random.random()#随机设置0~2之中任意一个整数,并加上0~1中随机数
time.sleep(sleep_time)
代理(Proxy)是一种特殊的网络服务,允许一个网络终端(一般为客户端)通过这个服务与另一个网络终端(一般为服务器)进行非直接的连接。形象地说,代理就是网络信息的中转站。代理服务器就像一个大的缓冲,能显著提高浏览速度和效率。
以下是使用代理IP获取网页的方法:
import requests
link = "[url]"
proxies = {'http':'http://xxx.xxxxx.xxxx:xxx'}
response = requests.get(link,proxies = proxies)
措施三并不推荐,一方面网络上的免费代理不稳定;另一方面,通过代理IP的服务器请求爬取速度慢。
在正式介绍之间,需要先熟悉并发与并行、异步与同步的概念。
并发是指一个时间段内发生若干事件的情况,并行是指同一时刻发生若干事件的情况。
例如:在单核处理器中,多个工作任务是并发运行的。单个任务若果在自己的时间段内未完成任务,就会切到另一个任务,然后在下次得到处理器使用权时继续执行任务。因为各个任务时间段很短且经常切换,所以感觉上是“同时”进行。在使用多核处理器时,在各个核中的任务能同时进行,这是真正的同时运行,也就是并行。
同步就是并发或并行的各个任务不是独自运行的,任务之间有一定的交替顺序,可能在运行玩一个任务得到结果后,另一个任务才会运行。
异步则是并发或并行的各个任务可独立运行,一个人物的运行不受另一个任务的影响。
多线程爬虫是以并发的方式执行的。Python本身的设计对多线程的执行有所限制,在Python中,一个线程的执行过程包括获取GIL(Global Interpreter Lock,全局变量锁)、执行代码直到挂起和释放GIL。
每次释放GIL,线程之间会进行锁竞争,而切换线程会消耗资源。由于GIL的存在,Python里的进程永远只能同时执行一个线程。这就是在多核处理器上Python的多线程效率不高的原因。
但是,由于网络爬虫是IO密集型,多线程能够提升效率。因为开启多线程能够在一个线程等待时自动切换至另一个线程,避免不必要的时间和CPU资源浪费,提升程序执行的效率。
流程简图如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WzEzjmO9-1575726189588)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\多线程爬虫流程.png)]
图片不显示或不清晰,请点击:https://www.processon.com/view/link/5c8f5501e4b02ce2e8926f4b
(1) 将爬取的网页源代码存储在一个里诶保重
(2) 同时使用多个线程对网页源代码表的网页内容进行解析
(3) 将解析之后的数据存储起来,完成多线程爬虫的全部过程
要想使用多线程爬虫,就需要先了解Python中使用多线程的两种方法:
(1) 函数式: 调用__thread 模块中的 start_new_thread() 函数产生新线程
(2) 类包装式 : 调用Threading库创建线程,从threading.Thread继承
__thread 中使用start_new_thread() 函数来产生新线程。语法如下:
_thread.start_new_thread(function,args[,kwargs])
#function: 线程函数
#args: 传递给线程函数的参数,必须为tuple类型
#kwargs: 可选参数
__thread提供了低级别、原始的线程,它相比于threading模块,功能比较有限。threading模块提供了Thread类来处理线程。并且使用threading能够有效控制线程,这将在网络爬虫中发挥更好的效果。其包括以下方法:
方法 | 说明 |
---|---|
run() | 表示线程活动的方法 |
start() | 启动线程活动 |
join([time]) | 等待至线程中止。阻塞调用线程直至线程的join() 方法被调用为止 |
isAlive() | 返回线程活动状态 |
getName() | 返回线程名称 |
setName() | 设置线程名 |
queue模块是Python内置的标准模块,可直接通过import queue引用。在queue模块中提供了三种同步的,线程安全的队列,分别由三个类Queue、LifoQueue、PriorityQueue表示,它们的唯一区别是元素取出的顺序不同,且后两者是Queue的子类。
Queue类表示基本的先进先出队列,创建方法是Queue.Queue(maxsize = 0),其中maxsize是整数,指明队列中能存放的数据个数的上限。
LifoQueue类表示后进先出队列,创建方法为Queue.LifoQueue(maxsize=0),maxsize含义与Queue类相同
PriorityQueue类表示优先级队列,按级别顺序取元素,级别低先取。优先级队列中的元素一般采取元组(优先级别,数据)的形式来存储。创建方法为:Queue.PriorityQueue(maxsize = 0)
(1) Empty : 当从空队列中取数据,可抛出此异常
(2) Full : 当向一个满队列中存元素,可抛出此异常
Queue类是Python标准库中线程安全的对垒实现的,提供了一个适用于多线程编程的先进先出的数据结构——队列,用于生产者和消费者线程之间的信息传递。
Queue类提供了以下数据存储和管理的常用方法:
用于创建队列,maxsize规定了队列的长度。一旦达到上限,再添加数据会造成阻塞,知道队列中的数据被消耗掉。若maxsize小于等于0,表示队列大小没限制,maxsize默认为0
若队列为空,返回True,否则返回False
如果队列已满,则返回Ture,否则返回False
返回队列大小
从队头获取并删除第一个元素,它有两个可选参数:
(1) block: 默认为True,即当队列为空,阻塞当前线程;当值为False,即队列为空,不阻塞线程,而是抛出Empty异常。
(2) timeout:设置阻塞的最大时长,默认为None
当block=True,timeout=None,表示无限期阻塞线程,知道队列中有一个可用元素;当timeout>0,表示阻塞的最大等待时长,如果超出时长队列中还没元素,则抛出Empty异常。当block=False,忽略timeout参数
在队尾添加一个元素,put()的3个参数介绍如下:
(1) item:必需的参数,表示添加元素的值
(2) block: 可选参数,默认为True,表示当队列已满时阻塞当前线程。如果取值为False,则当队列已满时抛出Full异常。
(3) timeout: 可选参数,默认为None
当block=True,timeout表示阻塞的时长;当timeout=None,表示无限期阻塞线程,直到队列中空出一个数据单元;若timeout>0,表示阻塞的最大时长,超过这个时长而没用可用数据单元出现,则引发Full异常。若block=False,忽略timeout参数
不等待地立即取出一个元素,相当于get(False)
不等待地立即放入一个元素,相当于put(False)
在完成一项工作后,task_done() 函数向任务已完成的队列发送一个信号
阻塞当前线程,直到队列中的所有元素都已被处理
多进程爬虫可以利用CPU的多核,进程数取决于计算机CPU的核心数。在该情况下,各个进程是并行的。Python多进程需要用到multiprocessing库。
使用multiprocessing 库有两种方式:(1) 使用Process + Queue 的方法 ,(2) 使用Pool + Queue的方法。
#导入Pool模块
from multiprocessing import Pool
#创建进程池,proesses设置进程数
pool = Pool(processes=n)
pool.map(func,iterable[,chunksize])
'''
使用map函数运行进程
func参数为需要运行的函数
iterable为迭代参数
chunksize
'''
multiprocessing的理念是像线程一样管理进程,和threading很像,且对于多核CPU的利用率比threading高很多。
当进程数量大于CPU的内核数,等待运行的进程会等到其他进程运行完成让出内核为止。故单核CPU无法进行多进程并行。在进行多进程之前,需要先了解CPU核心数。方法如下:
from multiprocessing import cpu_count
print(cpu_count())
当被操作对象数量达到百、千级别,可使用Pool发挥进程池的功效。Pool可提供指定数量的进程供用户调用。当有新的请求提交到Pool中时,若池未满,就会创建新的进程用来执行该请求;如果池中的进程数已达到规定的最大值,该请求会继续等待,知道池中有进程结束才能创建新的进程。
在使用Pool之前,需要了解阻塞和非阻塞的概念。
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态。阻塞要等到回调结果出来,在有结果之前,当前进程会被挂起。非阻塞为添加进程后,不一定等到结果出来就可添加其他进程运行。
协程是一种用户态的轻量级线程,使用协程的好处如下:
(1) 协程像一种在程序级别模拟系统级别的进程,由于是单线程,且减少切换,因此相对来说系统消耗少。
(2) 协程方便切换控制流,这简化了编程模型。协程能保留上一次调用的状态(所有局部状态的特定组合),每次过程重入,就相当于进入上一次调用的状态。
(3) 协程的高扩展性和高并发性.一个CPU可支持上万协程,很适合高并发处理。
协程的缺点有:
(1) 协程本质是单线程,不能同时使用单CPU的多核,需要和进程配合才能运行在多CPU上。
(2) 长时间阻塞的IO操作时不要使用协程,因为可能会阻塞整个程序。
使用协程实现爬虫的流程如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aKMZeDE6-1575726189589)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\协程爬虫流程.png)]
若图片不可见,请点击:https://www.processon.com/view/link/5c8f8c3fe4b0c996d3679f04
(1) 将要爬取的网址存储在一个列表中,由于针对每个网址都要创建一个协程,所以需要准备一个待爬取的网址列表。
(2) 为每个网址创建一个协程并启动该协程。协程会依次执行,爬取对应的网页内容。若一个协程在执行过程中出现网络阻塞或其他异常情况,则马上执行下一个协程。由于协程的切换不用切换线程上行下文,消耗较小,所以不用严格限制协程数量。每个协程负责爬取网页,并将网页中的目标数据解析出来。
(3) 将爬取的目标数据存储在列表中
(4) 遍历数据列表,将数据存储在本地文件。
gevent 是一个基于协程的Python网络库。引入方法及常用方法如下:
#引入gevent
import gevet
#创建并启动协程
gevent.spawn()
#等待所有协程执行完毕
gevent.joinall()
本章介绍的内容主要为网络爬虫的并发处理。对于多线程、多进程、多协程三者的选择可依据其应用环境。
(1) 多进程适用于CPU密集型代码,例如:各种循环处理、大量的密集并行运算等。
(2) 多线程适用于I/O密集型代码,例如:文件处理、网络交互。
(3) 多协程适用于大量不需要CPU的操作,例如: 网络I/O等
现实情况中,由于网络I/O的速度跟不上CPU的处理速度,所以限制爬虫程序发展的瓶颈在于网络I/O。结合以上三种方式的特点和用途,一般采用多线程和多协程技术实现爬虫程序。
文字处理主要涉及字符乱码、登陆表单处理、cookie记忆登陆等文本操作问题。
在进入正题前,先介绍关于Unicode与UTF-8的区别与联系,以及字符串类型转换的方法
关于这方面,详细请点击:https://wenda.so.com/q/1370999364067608
str字符串: 使用Unicode编码
bytes字符串: 使用将Unicode转化成的某种类型的编码,如:UTF-8,UTF-16
两者互转的方法如下:
方法 | 说明 |
---|---|
encode(‘[编码类型]’) | 将Unicode编码转换为其他编码的字符串 |
decode(‘[编码类型]’) | 将其他编码的字符串转换成Unicode编码 |
该问题的出现是因为网页默认字符编码不为UTF-8(网页编码一般为UTF-8)。解决方法是在**r=request.get(url)**后加上一行代码:r.encoding(‘[网站使用的字符编码]’)
当将某个字符串从GBK解码为unicode时,可采用:
str.decode('GBK')
但实际上可能会遇到UnicodeDecodeError异常。该类异常出现的原因是某些网站同一页面混用多种编码,于是出现非法字符。解决方法:采用ignore忽略这些非法字符
str.decode('GBK','ignore')
在decode方法中,decode的原型为decode([encoding],[errors=‘strict’]).第一个变量为编码类型,第二个变量为控制错误处理的方式,默认为strict,遇到非法字符会抛出异常。对于第二个变量的设置,有以下三种:
方法 | 说明 |
---|---|
ignore | 忽略其中的非法字符,仅显示有效字符 |
replace | 使用符号代替非法字符 |
xmlcharrefreplace | 使用XML字符引用代替非法字符 |
某些网页通过requests方法被获取后,输出text结果出现乱码。并且,网站字符集修改后仍存在乱码问题。这种情况可考虑网页使用gzip将网页压缩了,必须对其进行解码操作。
方法:使用t.content(该方式可自动解码gzip和deflate传输编码的响应数据)
在使用Python读取和保存文件时,一定要注明编码方式。由于windows系统的某些版本的默认编码方式为GBK(也即是ANSI),所以导致以其他格式编码的文本读取出现乱码。所以在读写时,要声明编码方式:
result_ANSI = open('test_ANSI.txt','r',encoding='ANSI')
对于JSON文件,由于其默认Unicode编码,若使用其他编码方式,需要设置 eusure_ascii=False
import json
title = '我们 love 你们'
with open('title.json','w',encoding='UTF-8') as f:
json.dump([title],f,ensure_ascii=False)
图像处理主要用于验证码的识别等涉及图像的问题。
光学字符识别(Optical Character Recogintion,OCR)是指对包含文本资料的图像文件进行分析识别处理,获取文字及版面信息的技术.一般包括以下几个过程:
针对不同格式的图像,有着不同的存储格式和压缩方式.目前,用于存取图像的开源项目有OpenCV 和 CxImage 等.
预处理主要包括二值化、噪声去除和倾斜校正。具体如下:
大多数情况下,使用摄像头拍摄的图像是彩色的。这种图像包含的信息总量非常丰富,需要进行简化。可将图像内容简单分为前景和北京,为让计算机更好地识别文字,需要对彩图进行处理,使图像简单定义为黑色的前景和白色的背景信息,这就是二值化图。
对不同的文档,噪声的定义可以不同。根据噪声的特征进行消除处理,叫做噪声去除。
通常情况下,用户拍色的照片较随意,拍照文档很有可能会产生倾斜。这时,需要使用文字识别软件进行校正。
将文档图片分段落、分行的过程叫做版面分析。由于实际文档的多样性和复杂性,目前没有一个固定的、最好的切割模型。
4.字符切割
由于拍照条件的限制,经常会早茶鞥字符粘连、断笔等情况,因此极大地限制了识别系统的性能。此时,需要文字识别软件具备字符且各功能
该功能早期有模板匹配,现在是以特征提取为主。由于文字的位移、笔画的粗细、断笔粘连、旋转等因素的影响,极大地增加了提取的难度。
通常,人们希望识别后的文字仍按照原文档图片那样排列,保持段落、位置、顺序不变,之后输出到Word文档或PDF文档,这个过程叫做版面恢复
不同的语言环境中,语言的逻辑顺序是不同的。因此,需要根据语言特征的上下文,对识别后的结果进行校正,这个过程就是后处理
Tesseract 是一个开源的OCR库,是目前公认的最优秀、最精确的开源OCR系统,具有精确度高、灵活性高等特点。它不仅可通过训练识别出任何字体(只要字体风格保持不变即可),而且可以识别出任何Unicode字符。
Tesseract 支持60种以上的语言,它提供了一个引擎和命令行工具。要想在系统中使用Tesseract,需要先安装Tesseract-OCR引擎。
打开命令行窗口,输入tesseract命令可验证是否安装成功。若成功,则输出tesseract的相关信息。
关于字库 : 在Tesseract的安装目录下,默认有个tessdata目录存放有语言字库文件。其中,chi_sim.traineddata存放的是中文字库,其余则为英文字库。
Tesseract是一个命令行工具,安装后只能通过tesseract命令在Python的外部运行。为能用Python实现图像识别,需要使用支持Tesseract-OCR引擎的第三方库pytesseract
安装pytesseract需要遵守如下要求:
(1) Python的版本必须为python 2.5+ 或 python 3.x
(2) 安装Python的图像处理库PIL(或 Pillow)
(3) 安装Tesseract-OCR引擎
pytesseract是一款用于光学字符识别(OCR)的Python工具,即从图片中识别和读取其中的嵌入的文字。在pytesseract库中,提供如下函数将图像转换成字符串:
image_to_string(image,lang=None,boxes=False,config=None)
#参数说明:
#image: 图像
#lang: 语言,默认使用英文
#boxes: 若为True,则将batch.nochop makebox命令被添加到tesseract调用中
#config:若被设置,则配置会添加到命令中
PIL是Python最常用的图像处理库,他不仅提供广泛的文件格式支持,而且具有强大的图像处理功能。
PIL库中一个非常重要的类是Image类,该类定义在与他同名的模块中。创建Image类对象的方法有很多,包括: 从文件中读取得到,从其他图像经过处理得到,创建全新的、下面对PIL库的常用函数和方法进行简单介绍。
该函数用于创建一个新图像。定义格式如下:
Image.new(mode,size,color=0)
#mode: 模式
#size: 大小
#当创建单通道图像,color是单个值
#当创建多通道图像,color是一个元组
#省略color参数,图像被填充为全黑
#color=None 图像不被初始化
该函数用于打开并识别给定的图像文件。定义格式如下:
open(fp,mode='r')
#fp: 字符串形式的文件名称
#mode:可省略,但只能为'r'
#若载入文件失败,则引起IOError异常,否则返回一个Image类对象
#实际上,函数会延迟操作,实际的图像数据并不会马上从文件中读取,而是等到需要处理数据时才会被读取。此时可调用load() 函数进行强制加载。
创建图像对象后,可通过Image类提供的方法处理这些图像。常用的方法及说明如下:
该方法将以特定的图片格式保存图片。语法格式如下:
save(self,fp,format=None,**params)
#多数情况下,可省略图片格式。此时,save() 方法会根据文件扩展名来选择相应的图片格式
image.save("test.jpg")
#或者
image.save('test.jpg','JPG')
该方法可对图像的像素值进行变换。语法格式如下:
point(self,lut,mode=None)
#多数情况下,可使用函数(带一个参数)作为参数传递给point() 方法,图像的每个像素都会使用这个函数进行变换。
point(lambda i:i*1.2) #每个像素乘1.2
#需注意的是:如果图像的模式为"I"(整数)或"F"(浮点数),则必须使用函数,且必须具有如下格式
argument(参数)*scale(倍率)+offset(偏移量)
#例如:
out = im.point(lambda i:i*1.2+10)#映射浮点图像
'''
几个实例:
'''
#对图片进行阈值过滤和降噪处理
image = Image.open(file)
image = Image.point(lambda x: 0 if x<143 else 255)#当x=255 ,颜色为白色,x=0 颜色为黑色
image.save(image)
#识别图像中的中文字符
text = image_to_string(data,lang="chi_sim")
方法如下:
from PIL import Image
from io import BytesIO
r = request.get('[url]')#获取图片信息
image = Image.open(BytesIO(r.context))#二进制处理,使用Image函数打开
image.save('[jpg文件或其他]')
Scrapy使用Python实现的开源爬虫框架,其使用Twisted(主要对手为Tornado)异步网络框架来处理网络通信(该网络框架可加快下载速度,并包含各种中间件接口,可灵活地完成各种需求)。
其运行速度快、操作简单、可扩展性强。功能强大,支持自定义Item和pipeline数据管道;支持在Spider中指定domain(网页域范围)以及相应的Rule(爬取规则);支持XPath对DOM的解析等。而且Scrapy还有自己的shell,可方便地调试爬虫项目和查看爬虫运行结果。
它的用途广泛,可用于爬虫开发、数据挖掘、数据监测、自动化测试等领域。
如图所示为Scrapy的架构图:https://www.processon.com/view/link/5c90d814e4b09a16b9a87cd9
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XQ7OcjwZ-1575726189590)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\Scrapy框架的架构图.png)]
由图可知,Scrapy框架主要包含以下组件:
(1) Scrapy Engine(引擎): 负责Spiders、Item Pipeline、Downloader、Scheduler之间的通信,包括信号和数据的传递等
(2) Scheduler(调度器):负责接收引擎发送的Request请求,并按照一定的方式进行整理排列和入队,当引擎需要时,交换引擎。
(3) Downloader(下载器):负责下载Scrapy Engine发送的所有Request(请求);将其获取到的Request(响应)交还给Scrapy Engine,由Scrapy Engine教给Spider处理。
(4) Spider(爬虫):负责处理所有Response,从中分析提取数据,获取Item字段需要的数据,并将需要跟进的URL提交给引擎,再次进入Scheduler(调度器)
(5) Item Pipeline(管道):费则处理Spiders中获取的Item数据,并进行后期处理(详细分析、过滤、存储等)
(6) Downloader Middlewares(下载中间件):可自定义扩展下载功能的组件
(7) Spider Middlewares(Spider中间件):可自定义拓展Scrapy Engine 和 Spiders中间通信的功能组件。
Scrapy的运作流程靠引擎控制,其过程如下:
(1)引擎向Spiders请求第一个爬取的URL(s)
(2)引擎将要爬取的URL封装成Request并交给调度器
(3)引擎向调度器请求下一个爬取的URL(s)
(4)调度器返回下一个要爬取的Request给引擎,引擎将Request通过下载中间件转发给下载器
(5)一旦页面下载完毕,下载器生成一个该页面的Response,并将其通过下载中间件发送给引擎
(6)引擎从下载器中接收Response并通过Spider中间件发送给Spider处理
(7)Spider处理Response并返回爬取到的Item及新的Request给引擎
(8)引擎将Item给Item Pipeline,将Request给调度器
(9)重复(2),直至调度器中没有更多的Request
使用Scrapy框架制作爬虫一般需要以下4个步骤:
(1)新建目标(scrapy startproject xxx):创建爬虫项目
(2)明确目标(编写items.py):明确想爬取的目标
(3)制作爬虫(spiders/xxspider.py):制作爬虫,开始爬取网页
(4)存储数据(pipeline.py):存储爬取内容(一般通过管道进行)
以下是各步骤详解:
在目标目录下使用cmd命令,打开终端。输入创建Scrapy项目的命令创建项目。命令格式如下:
scrapy startproject 项目名
命令创建成功后,可看到项目目录为如下结构:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5MaUmfRp-1575726189591)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\Scrapy项目目录.png)]
下面介绍各个文件的作用:
(1) scrapy.cfg : 配置文件,用于存储项目的配置信息
(2) mySpider: 项目的Python模块,将从这里引用代码
(3) items.py : 实体文件,用于定义项目的目标实体
(4) middlewares.py : 中间件文件,用于定义Spider中间件
(5) pipelines.py : 管道文件,用于定义项目使用的管道
(6) settings.py : 设置文件,用于存储项目的设置信息
(7) spiders: 存储爬虫代码的目录
Scrapy使用Item(实体)来表示要爬取的数据。Item定义结构化数据字段,类似于Python中的字典dict,但是提供一些额外的保护以减少错误。
Scrapy框架提供基类scrapy.Item用来表示实体数据。一般需要创建一个继承自scrapy.Item的子类,并为该子类添加类型为scrapy.Field的类属性来表示爬虫项目的实体数据(可理解成类似于ORM的映射关系)
打开项目中的items.py文件,可看到文件中自动生成继承自scrapy.Item的项目Item类。用户只需修改项目Item类的定义,为它添加属性即可。例如:
import scrapy
class MySpiderItem(scrapy.Item):
name = Field() #获取名字
title = Field() #获取标题
info = Field() #获取详细信息
要先在项目中创建一个爬虫,打开终端,在项目的spiders目录中创建。创建爬虫的格式如下:
scrapy genspider 爬虫名称 爬虫域
在创建爬虫的命令中,需要为爬虫起一个名字,并规定该爬虫要爬取的网页域范围,即爬虫域。
结果示例如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-FVzcZdAn-1575726189592)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\创建爬虫.png)]
从代码中,可知自动创建的爬虫类名称为AQISpider,他继承自scrapy.Spider类。scrapy.Spider是Scrapy提供的爬虫基类,用户创建的爬虫累都需要从该类继承。爬虫类中需要定义三个属性和一个方法,介绍如下:
(1) name 属性:表示这个爬虫的识别名称。爬虫的名称必须唯一,不用的爬虫需要定义不同的名称。
(2) allow_domains 属性:表示爬虫搜索的域名范围,也就是爬虫的约束区域。该属性规定爬虫只能爬取这个与名下的网页,不在该域名下的URL会被忽略。
(3) start_urls 属性: 表示爬取的起始URL元组或列表。爬虫第一次下载的数据将会从这些URL开始,其他子URL将会从这些起始URL中继承性的生成。
(4) parse(self,response) 方法:用于解析网络响应。该方法在每个初始URL完成下载后的调用,调用时传入从该URL返回的Response对象作为唯一参数。parse() 方法主要有两个功能:
一、解析返回的网页数据(response.body),提取结构化数据(生成item)
二、生成访问下一页数据的URL
运行爬虫的命令格式如下:
scrapy crawl 爬虫名称
一个Scrapy项目中,可存在多个爬虫,各个爬虫在执行时,就是按照name属性来区分的。
命令执行后,如果打印的日志出现如下提示信息,则代表爬取过程执行完毕。
[scrapy.core.engine] INFO: Spider closed(finished)
通过前两个步骤,已成功爬取到网页源码,下面可从源码中提取数据。在分析网页结构和数据后,需要对项目中部分文件的代码进行修改。
首先,将items.py目录中已定义的与爬虫爬取具体数据有关的类引入到爬虫文件中。
from 项目名.items import 类名
然后修改爬虫文件中的parse() 方法。(具体形式参考“6.数据解析”章节)
使用Scrapy框架制作爬虫的最后一步就是将获取到的目标数据进行永久性存储。Scrapy保存数据最简便的方法主要有4中,在运行爬虫的命令后使用 -o 选项可输出指定格式的文件,这些输出文件的示例如下:
#输出JSON格式,默认Unicode编码
scrapy crawl 爬虫名 -o JsonName.json
#输出JSON Lines格式,默认Unicode编码
scrapy crawl 爬虫名 -o JsonName.jsonl
#输出CSV格式,使用逗号表达式,可用Excel打开
scrapy crawl 爬虫名 -o CsvName.csv
#输出XML格式
scrapy crawl 爬虫名 -o XmlName.xml
命令用途 | 命令格式 | 举例 |
---|---|---|
创建项目 | scrapy startproject 项目名称 | scrapy startproject mySpider |
创建爬虫 | scrapy genspider 爬虫名称 爬取域 | scrapy genspider itcast “itcast.cn” |
运行爬虫 | scrapy crawl 爬虫名称 | scrapy crawl itcast |
保存数据 | scrapy crawl 爬虫名称 -o 保存数据的文件名 | scrapy crawl itcast -o teachers.json |
Scrapy shell是一个交互式终端,可用于在不启动爬虫的情况下尝试及调试爬取的代码。也可用于测试Xpath或CSS表达式,查看它们的工作方式以及从爬取的网页中提取的数据。在编写爬虫时,Scrapy Shell提供交互性测试表达式代码的功能,免去每次修改后运行爬虫的麻烦,在开发和调试爬虫阶段能发挥巨大作用。
Scrapy Shell通常使用Python终端,如果安装IPython,则优先使用IPython。原因如下:IPython终端比标注终端功能更强,能提供代码自动补全、高亮输出、及其他提高易用性的特性。
命令如下:
scrapy shell <URL>#URL为要爬取的网页地址
#提示信息中,"Crawled(200)"表示成功访问网页结果
Scrapy Shell可看成一个在Python终端(或IPython)基础上添加了扩展功能的Python控制台程序,这些扩充包括若干功能和内置对象。
Scrapy Shell提供的功能函数主要包括以下3种:
(1) help: 打印可用对象和快捷命令的帮助列表
(2) fetch(request_or_url):根据给定的请求request或URL获取一个新的response对象,并更新原有的相关对象。
(3) view(response):使用本机的浏览器打开给定的response对象,该函数会在response的body中添加一个标签,使得外部链接(例如图片及css)能正常显示。须注意的是:该函数还会在本地创建一个临时文件,且该临时文件不会被自动删除。
使用Scrapy Shell下载指定页面时,会生成一些可用的内置对象,例如response对象heselector对象(针对Html 和 XML内容)。这些对象包括:
(1) Crawler: 当前Crawler对象
(2) spider:处理URL的spider
(3) request:最近获取的页面的request对象。可使用replace()修改该request,也可使用fetch功能函数来获取新的request
(4) response:包含最近获取的页面的Response对象
(5) sel:根据最近获取的response构建的Selector对象
(6) settings:当前的Scrapy settings
当Scrapy Shell载入页面后,将得到一个包含Response数据的本地response变量。子啊终端输入response.body可看到终端输出response的包体,输入response.headers可看到response的包头。
输入response.selector时,将获取一个response初始化的类Selector的对象(对HTML及XML内容),此时可通过使用response.selector.xpath()或response.selector.css()来对response进行查询。另外,Scrapy还提供了一些快捷方式,例如:response.xpath()或response.css()同样生效
与Scrapy选择器相关的有4个常用方法,具体介绍如下:
常用方法 | 说明 |
---|---|
xpath() | 传入XPath表达式,返回该表达式所对应的所有结点的Selector对象列表 |
css() | 传入CSS表达式,返回该表达式所对应的所有结点的selector list列表,语法同BeautifulSoup4 |
extract() | 返回该选择器对象(或对象列表)对应的字符串列表 |
re() | 根据传入的正则表达式对选择器对象(或对象列表)中的数据进行提取 |
其中,前两个方法返回的都是选择器列表,最常用的是xpath()方法;后两个方法是返回选择器对象(或对象列表)的字符串内容
Spiders定义爬取网站的方式,包括爬取的动作(例如: 是否跟进链接)以及如何从网页内容中提取结构化数据(提取item).换句话说,Spiders就是定义爬取的动作及分析网页的地方。
Scrapy框架提供scrapy.Spider作为爬虫的基类,所有自定义的爬虫必须从这类派生。scrapy.Spider类的主要属性和方法介绍如下:
定义爬虫名称的字符串.爬虫名称用于Scrapy定位和初始化一个爬虫,所以必须是唯一的。通常,使用待爬取网站的域名作为爬虫名称。例如:爬取mywebsite.com的爬虫通常被命名为mywebsite
包含爬虫允许爬取的域名列表,是可选属性
表示初始URL元组或列表.当没有指定特定的URL时,Spider将从该列表中开始爬取.
初始化方法,负责初始化爬虫名称和start_urls列表
负责生成Requests对象,交给Scrapy下载并返回response.
该方法必须返回一个可迭代对象,该对象包含Spider用于爬取的第一个Request,默认使用start_urls列表中的第一个URL
负责解析response,并返回Item或Requests(需指定回调函数).返回的Item传给Item Pipline持久化,erRequests则交给Scrapy下载,并有指定的回调函数处理(默认parse()).然后持续循环,直到处理完所有的数据为止.
负责发送日志信息
当Item在Spider中被收集后,会被传递到Item Pipeline(管道).用户可在Scrapy项目中定义多个管道,这些管道按定义的顺序依次处理Item
每个管道都是一个Python类,在这个类中实现一些操作Item的方法.其中,有的方法用于丢弃重复的Item,有的方法用于将Item存储到数据库或文件等.以下时Item Pipeline的部分典型应用:
(1) 验证爬取的数据,检查Item包含某些字段,例如name字段
(2) 查重,并丢弃重复数据
(3) 将爬取的结果保存到文件或数据库中
每个Item Pipeline组件都是一个独立的Python类,该类中的 process_item() 方法必须实现,每个Item Pipeline组件都需要调用process_item()方法
process_item() 方法必须返回一个Item(或任何继承类)对象,或者抛出DropItem异常,被丢弃的Item将不会被之后的Pipeline组件处理.
该方法定义如下:
process_item(self,item,spider)
#item: 表示被爬取的Item对象
#spider:表示爬取该Item的Spider对象
示例如下:实现名为SomethingPipeline的自定义Item Pipeline类
import something
class SomethingPipeline(object):
def __init__(self):
#可选实现,做参数初始化
#doing something
def process_item(self,item,spider):
#item(Item对象)--被爬取的item
#spider(Spider对象)--爬取该item的spider
#这个方法必须实现,每个item pipeline组件都需要调用该方法.
#这个方法必须返回一个Item对象,被丢弃的item将不会被之后的pipeline组建所处理
return item
def open_spider(self,spider):
#spider(Spider对象)--被开启的spider
#可选实现,当spider被开启时,这个方法被调用
def cose_spider(self,spider):
#spider(Spider对象)--被关闭的spider
#可选实现,当spider被关闭时,这个方法被调用
在Spider settings(设置)中可以定制各个Spider组件的行为,包括核心组件、扩展组件、管道及Spider组件等。
以下是常用的设置及其含义:
使用Scrapy实现bot名称,也叫项目名称,该名称用于构造默认的User-Agent,同时也用来记录日志。默认名称是scarpybot。当使用startproject命令创建项目时,该值会被自动赋值。
设置Item Pipeline同时处理每个responsedeitem的最大值,默认为100
设置Scrapy downloader并发请求的最大值,默认为16
设置Scrapy HTTP Request使用的默认header,其默认值如下:
{
'Accept':'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8','Accpet-Language':'en',
}
设置爬取网站最大允许的深度值.默认为0,表示无限制.
设置下载器在下在同一个网站下一个页面前需要等待的时间.噶选项可用来限制爬取的速度,减轻服务器压力.默认为0,支持小数.例如:
DOWNLOAD_DELAY = 0.25 # 250 ms of delay
默认情况下,scrapy在两个请求间不会设置一个固定的等待值,而是使用0.5~1.5之间的一个随机值*DOWNLOAD_DALAY的结果作为等待间隔.
设置下载器的超时时间(单位: 秒),默认值为180
该设置项的值是一个保存项目中启用Pipeline及其顺序的字典.该字典默认为空.字典的键表示Pipeline的名称,值可以是任意值,但习惯设置在0~100范围内.值越小则优先级越高.
以下是一个设置项的样例:
ITEM_PIPELINES={
'mySpider.pipeline.SomethingPipeline':300,
'mySpider.pipeline.ItcastJsonPipeline':800,
}
设置是否启用logging,默认为true
设置logging的编码,默认是utf-8
设置log的最低级别,可选级别有CRITICAL,ERROR,WARNING,INFO,DEBUG,默认值为DEBUG
(12)USER_AGENT
设置爬取网站时使用的默认User-Agent,除非被覆盖,它的默认值是Scrapy/VERSIN(+http://scrapy.org)
(13)PROXIES
设置爬虫工作时使用的代理,示例如下:
PROXIES=[
{'ip_port':'111.11.228.75:80','password':''}
{'ip_port':'120.98.243.20:80','password':''}
{'ip_port':'101.71.27.120:80','password':''}
{'ip_port':'122.96.59.104:80','password':''}
]
改设置项禁用Cookies,为不让网站根据请求的Cookies判断出用户的身份是爬虫,一般将Cookies的功能禁用.
Downloader Middlewares(下载中间件)是处于引擎和下载器之间的一层组件,多个下载中间件可被同时加载运行.
在引擎传递请求给下载器的过程中,下载中间件可对请求进行处理(例如:增加Http header信息,增加proxy信息等).在下载器完成网络请求,传递响应给引擎的过程中,下载中间件客队响应进行处理(例如: 进行gzip的解压)
编写下载器中间件十分简单.每个中间件组建都是一个Python类,其中定义process_request()方法和process_respose()方法中的某一个或全部.这两种方法介绍如下:
用于对请求进行处理,在每个request通过下载器中间件时被调用.该方法的参数介绍如下:
(1) request : 要处理的Request对象
(2) spider : 该request对应的Spider对象
该方法可能返回None,一个Response对象,或者一个Request对象,也可能抛出IgnoranceRequest异常.针对这四种情况,Scrapy有不同的处理方式,具体介绍如下:
(1) 如果返回None,Scrapy将继续处理该request,执行其他中间件的相应方法,直到合适的下载器处理函数被调用,该request被执行(即其Response被下载)
(2) 如果返回Response对象,Scrapy将不会调用任何其他的process_request() 方法、process_exception()方法,或相应的的下载函数,而是返回该Response。已安装的中间件的process_response()方法则会在每个Response返回时被调用.
(3) 如果返回Request对象,Scrapy将停止调用process_request()方法并重新调度返回的request。当新返回的Request被执行后,相应的中间件链将会根据下载的Response被调用。
(4) 如果抛出IgnoreRequest异常,则安装的下载中间件的process_exception()方法会被调用。如果没有任何一个方法处理该异常,则request的errback(Request.errback)方法会被调用。如果没有代码处理抛出的异常,则该一场被忽略且不记录(不同于其他异常的处理方式).
用于对response进行处理,当下载器完成http请求,传递相应给引擎的时候调用。
该方法有3个参数,分别介绍如下:
(1) request:是一个Request对象,表示response所对应的request
(2) response:是一个Response对象,表示被处理的response
(3) spider:是一个Spider对象,表示response所对应的spider
该方法有三种执行结果,分别为:返回一个Response对象,返回一个Request对象,抛出一个IgnoreRequest异常。针对这3种结果,Scrapy有不同的处理方式,具体介绍如下:
(1) 如果返回一个Request对象,则中间件链停止,返回的request会被重新调度下载。处理方式类似process_request()返回request所做的操作.
(2)如果返回一个Response对象(可以与传入的response相同,也可是全新的对象),改Response会被处于链中的其他中间件的process_response()方法处理
(3) 如果抛出一个IgnoreRequst异常,则调用request的errback(Request.errback)方法。如果没有代码处理抛出的异常,则该异常被忽略其不记录(不同于其他异常的处理方式)
Scrapy框架在Scrapy.spiders模块中提供了CrawlSpider类专门用于自动爬取,CrawlSpider类是Spider的派生类。Scrapy类设计原则是只爬取start_url列表中的网页,而CrawlSpider类定义了一些规则来提供跟进link的方便机制,对于从爬取的网页中获取link并继续进行爬取工作更适合。
以下命令可快速创建一个使用CrawlSpider模版的爬虫
scrapy genspider -t crawl [爬虫名] [爬取的域名]
# -t: 表示模板
# crawl: 表示模板名称
在创建爬虫时,使用了模板.对于模板的使用,有以下几点:
命令 | 说明 |
---|---|
scrapy genspider -l | 查看该爬虫项目下拥有的爬虫模版 |
scrapy -d template(如: crawl) | 查看爬虫模版的详细内容 |
scrapy genspider (-t template) name domain | 创建spider(不指定模版,则使用默认的basic模版) |
另外,爬虫name不能使用项目name,也不能使用已存在的spider的name,否则系统报错。如果想覆盖以前的spider,可在genspider 后加 —-force 参数,则后面的name是被覆盖spider的name
也可为框架添加自己设计的爬虫模版,模版的位置与项目模版文件夹并列,名称为spiders。可在爬虫模版中添加自己常用的方法,也可设计一个简单的类框架,一是用所需要采集的页面规则。
因为CrawlSpider继承Spider,所以具有Spider的所有函数,此外,它还定义了自己的若干属性和方法。CrawlSpider类的新属性名为rules,介绍如下:
rules属性: 一个包含一个或多个Rule对象的元组。每个Rule对爬取网站的动作定义了特定表现。若多个Rule匹配同一个链接,则根据他们在本属性中定义的顺序,使用第一个Rule。
CrawlSpider类定义的若干方法及其说明如下:
名称 | 说明 |
---|---|
init_() | 负责初始化,并调用__compile_rules()方法 |
parse() | 该方法进行重写,在实现体中直接调用方法_parse_response(),并把parse_start_url()方法作为处理response的方法 |
parse_start_url() | 该方法主要作用是处理parse返回的response,例如:提取出需要的数据等。该方法需要返回item、response或者他们的可迭代对象 |
_requests_to_follow() | 该方法作用是从response中解析出目标URL,并将其包装成request请求。该请求的回调方法是_response_downloaded(),这里为request的meta值添加了rule参数,该参数的值是这个url对应rule在rules中的下标 |
_response_downloaded() | 该方法是方法_requests_to_follow()的回调方法,其作用是调用_parse_response()方法,处理下载器返回的response,设置response的处理方法为rule.callback()方法 |
_parse_response() | 该方法将response交给参数callback代表的方法去处理,然后处理callback()方法的requests_or_item。再根据rule.follow and spider._follow_links 来判断是否继续采集,如果继续,就将response交给_resquests_to_follow()方法,根据规则提取相关的链接。spider_follow_links的值是从settings的CRAWLSPIDER_FOLLOW_LINKS值获取得到 |
_compile_rules() | 这个方法是将rule中的字符串表示的方法改成实际的方法,方便以后使用 |
具体工作原理由于比较复杂,暂不写。可百度自查。
CrawlSpider类使用rules属性来决定爬虫的爬取规则,并将匹配的URL请求提交给引擎。所以在正常情况下,CrawlSpider不需单独手动返回请求。
在rules属性中可包含一个或多个Rule对象,每个Rule对象都对爬取网站的动作定义某种特定操作。如果包含了多个Rule对象,那么每个Rule轮流处理Response。每个Rule对象可规定不同的处理item的parse_item() 方法,但一般不使用Spider类已定义的parse() 方法。如果多个Rule对象匹配相同的链接,则根据规则在本集合中被定义的顺序,第一个会被使用。
Rule类的构造方法定义如下:
class scrapy.spiders.Rule(
link_extractor,
callback=None,
cb_kwargs=None,
follow=None,
process_links=None,
process_reqest=None
)
#link_extractor : 是一个 Link Exractor 对象,用于定义链接的解析规则
#callback: 指定回调方法的名称.从link_extractor中获取到链接时,该参数所指定的的值作为回调方法.该回调方法必须接收一个Response对象作为其第一个参数,并返回一个由Item/Request对象或它们的子类所组成的列表
'''
注意: 当编写爬虫规则时,避免使用parse() 作为回调函数.由于Crawlspider使用parse()方法来实现其逻辑,如果覆盖parse() 方法,crawl spider将会运行失败
'''
#cb_kwargs: 一个字典,包含传递给回调方法的参数,默认值时None
#follow : 布尔值,制定根据本条rule从Response对象中提取的链接是否需要跟进。如果callback参数值为None,则follow默认值为True,否则默认值为False
#process_links: 指定回调方法的名称,该回调方法用于处理根据link_extractor从Response对象中获取到的链接列表。该方法主要用来过滤链接。
#process_request: 指定回调方法的名称,该回调方法用于根据本ruel提取出来的Request对象,其返回值必须是一个Request对象或None(表示将该request过滤掉)
LinkExtractor类的唯一目的是从网页中提取需要跟踪爬取的链接。它按照规定的提取规则来提取链接,这个规则只适用于链接,不适用于普通文本。
Scrapy框架在scrapy.linkextractors模块中提供LinkExtractor类专门用于表示链接提取类,但是用户也可自定义一个符合特定需求的链接提取类,只需让它实现一个简单的接口即可。
每个LinkExtractor都需要保护一个公共方法extract_links(),该方法接收一个Response对象作为参数。并返回一个元素类型为scrapy.link.Link的列表。在爬虫工作的过程中,链接提取类只需要实例化一次,但从响应对象中提取链接时会多次调用extract_links()方法
链接提取类一般与若干rules结合一起用于CrawlSpider类中,但在其他与CrawlSpider类无关的场合也可使用该类提取链接。
在Scrapy框架中默认的链接提取类是LinkExtractor类,他其实是对scrapy.linkextractors.lxmlhtml.LxmlLinkExtractor类的饮用,这两个类等价。
LinkExtractor类的构造方法如下:
class scrapy.linkextractors.LinkExtractor(
allow=(),
deny=(),
allow_domains=(),
deny_domains=(),
restrict_xpaths=(),
tags=('a','area'),
attrs=('href'),
canonicalize=False,
unique=True,
process_value=None,
deny_extensions=None,
restrict_css=(),
strip = True
)
#allow:其职位一个或多个正则表达式组成的元组,只有匹配这些正则表达式的URL,才会被提取。如果allow参数为空,则会匹配所有链接。
#deny:其值为一个或多个正则表达式组成的元组,满足这些正则表达式的URL会被排除不优先提取(优先级高于allow参数)。如果deny参数为空,则不排除任何URL
#allow_domains:其值是一个或多个字符串组成的元组,表示会被提取的链接所在的域名
#deny_domains:其值是一个或多个字符串组成的的元组,表示被排除不提取的链接所在的域名
#restrict_xpaths:其值是一个或多个Xpath表达式组成的元组,表示只在符合该Xpath定姨爹文字区域搜寻链接
#tags:用于识别要提取的链接标签,默认值为('a','area')
#atts:其值是一个或多个字符串组成的的元组,表示在提取链接时要识别的属性(仅当该属性在tags规定的标签里出现时),默认值为('href')
#canonicalize:表示是否将提取到的URL标准化,默认False。由于使用标准化后的URL访问服务器与使用原URL访问得到的结果可能不同,所以最好保持使用它的默认值False
#unique:表示是否要对提取的连接进行去重过滤,默认True
#proces_value: 负责对提取的链接进行处理的函数,能对链接进行修改并返回一个新值,如果返回None则忽略该链接,如果不对该参数赋值,则使用它的默认值lambda x:x
#deny_extensions:其值是一个字符串或字符串列表,表示提取链接时应被排除的文件扩展名。例如: ['bmp','gif','jpg',]变送hi排除包含有这些扩展名的URL地址
#restrict_css:其值是一个或多个css表达式组成的元组,表示只在符合该CSS定义的文字区域搜寻链接。
#strip:表示是否要将提取的链接地址前后的空格去掉,默认为True
Scrapy是一个通用的网络爬虫框架,其功能较完善,但不支持分布式,即无法在多台设备上同时运行。为解决这个问题,Scrapy–Redis提供一些基于Redis数据库的组件,应用到Scrapy框架中,从而实现分布式爬取。
所谓分布式爬虫,从字面意思上可理解为集群爬虫。也即是说,当由多个爬虫任务时,可用多台机器同时运行,速度更快、更高效。
Scrapy-Redis 是一些提供给Scrapy使用的组件,这些组件以Redis数据库为基础,便于实现Scrapy的分布式爬取。再次强调,Scrapy-Redis仅仅只是一些组件,而非完整的框架。
Scrapy-Redis与Scrapy的关系可理解为前者作为第三方,可更好帮助后者实现某些功能
图片若不清楚,请点击:https://www.processon.com/view/link/5c9ae2d7e4b044178a9d7346
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1tPbPkXB-1575726189593)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\Scrapy-Redis架构图.png)]
与之前的Scrapy架构相比,Scrapy-Redis在此基础上增加了Redis和Item Proecesses.这两个组件介绍如下:
(1) Redis(Remote Dictionary Server):开源的、使用ANSI C语言编写的、支持网络交互、可基于内存亦可持久化的Key-Value数据库,提供多语言API。注意,该数据库仅可用作存储URL,不关心爬取的数据,不要与MongoDB和MySQL相混淆。
(2) Item Processes: Item集群
本架构可用作爬虫请求的调度问题、去重问题,以及Items的存储,都交给Redis进行处理,这样不仅保持之前的记录,而且允许爬虫产生中断。因为每个主机上的爬虫进程都访问同一个Redis数据库,所以调度和判重都会统一进行管理,达到分布式爬虫的目的。
基于Scrapy-Redis的运作流程如下:
(1)引擎(Scrapy Engine)向爬虫(Spiders)请求第一个要爬取的URL
(2)引擎从爬虫中获取到第一个要爬取的URL,封装成请求(Request)并交给调度器(Scheduler)
(3)调度器访问Redis数据库对请求进行判重,若不重复,就把这个请求添加到Redis中
(4)当调度条件满足,调度器会从Redis中取出Request,交给引擎,引擎将这个Request通过下载中间件转发给下载器(Downloader)
(5)一旦页面下载完毕,下载器生成一个该页面的响应(Response),并将其通过下载中间件发送给引擎。
(6)引擎从下载器中接收响应,并通过爬虫中间件(Spider Middlewares)发送给爬虫处理
(7)Spider处理Response,并返回爬取到的Item及新的Request给引擎
(8)引擎将爬取的Item通过Item Pipeline 给Redis数据库,将Request给调度器
(9)从第(2)步开始重复,直到调度器中没有更多的Request为止
基于Redis的特性,Scrapy-Redis扩展了如下组件:
Scrapy框架改造了Python本来的双向队列,形成自己的Scrapy Queue,但是Scrapy的多个爬虫不能公用待爬队列Scrapy Queue,无法支持分布式爬取。Scrapy-Redis则把Scrapy Queue换成Redis数据库,由一个Redis数据库统一存放要爬取的请求,从而能让多个爬虫到同一个数据库中读取数据。
在Scrapy框架中,跟“待爬队列”直接相关的是Schduler,它负责将新的请求添加到队列(加入Scrapy Queue),以及去除下一个要爬的请求(从Scrapy Queue中取出)。Scheduler将待爬取队列按照优先级存储在一个字典结构内,例如:
{
优先级0 : 队列0
优先级1 : 队列1
优先级2 : 队列2
}
在添加请求时,根据请求的优先级(访问priority属性)来决定该进入哪个队列,出队列则按照优先级较小的优先出列。为管理这个高级的队列字典,Scheduler需要提供一系列方法,在分布是爬虫中,Scrapy使用的是Scrapy-Redis的Scheduler组件。
Scrapy中使用集合实现请求的去重功能,他会把已经发送出去的请求指纹(请求的特征值)放到一个集合中,然后把下一个请求指纹拿到集合中比对。如果该指纹存在于集合中,则说明这个请求发送过;若不存在,则继续操作。
Scrapy-Redis中的去重是由Duplication Filter组件实现的,该组件利用Redis中的set集合不重复的特征,实现该功能。首先,Scrapy-Redis调度器接收引擎传递过来的请求,然后将这个请求指纹存入set集合中检查是否重复,并把不重复的请求加入到Redis的请求队列中。
当爬虫爬取到Item数据,引擎会把Item数据交给Item Pipeline(管道),而管道再把Item数据保存到Redis数据库的Item队列中。
Scrapy-Redis对Item Pipeline组件进行了修改,它可很方便地根据键(Key)从Item队列中提取Item,从而实现Item Processes集群。
Scrapy-Redis中的爬虫使用重写的RedisSpider类,该类继承Spider和RedisMixin两个类,其中Redismixin是用来从Redis读取URL的类。当生成一个Spider类继承自RedisSpider类时,调用setup_redis()函数,这个函数会去连接Redis数据库,当满足一定条件,会设置如下两个信号:
(1)一个爬虫端(可以是多个机器)空闲时的信号。这个信号交给引擎,由引擎去判断爬虫当在是否处于空闲状态。如果是空闲状态,就会调用spider_idle()函数,这个函数调用schedule_next_request()函数请求交给爬虫,保证爬虫一直是活跃状态,并抛出DontCloseSpider异常。
(2)另一个是抓到一个Item时的信号,这个信号依然会交给引擎判断,如果检测到确实爬取到Item,则会调用item_scraped()函数,该函数会调用schedule_next_request()函数或取下一个请求。
在准备使用Scrapy-Redis开发前,需要按照如下要求为其配置开发环境:
(1)Python的版本为2.7或3.4+
(2) Redis版本>=2.8
(3) Scrapy 版本>=1.0
(4) redis.py: Python跟Redis交互的组件,版本>=2.10
环境配置的几个步骤:
终端命令安装命令如下:
pip install scrapy-redis
在使用Scrapy-Redis之前,需要保证计算机系统中已成功安装Redis数据库。Redis官方不提供Windows版本,因为在服务器领域Linux已得到广泛应用。尽管如此,官方提供了微软提供的git库,下载及解压教程可参考百度。
几点需要说明:
redis-server.exe redis.windows.conf
#或者
redis-server redis.windows.conf
成功启动如图:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xETgmH5x-1575726189594)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\Redis启动成功.png)]
**注意 :**可将redis目录添加到系统的环境变量中,免得以后频繁输入路径。
redis-cli.exe -h 127.0.0.1 -p 6379
#或者
redis-cli -h 127.0.0.1 -p 6379
#-h参数后跟的是IP地址,-p参数后面跟的是端口,服务器默认端口为6379
当成功启动客户端,cmd框内显示 类似与 127.0.0.1:6379> 形式的字符串,此时可向数据库中添加键值对。形式如下:
set 键名值 #设置键值对
get 键名 #取出键对应的值
Redis的配置文件位于其安装目录下,文件名为redis.conf(Windows系统下为redis.windows.conf)
在配置文件中,默认绑定的主机地址是本机地址,即 bind 127.0.0.1。也就是说,当访问Redis服务时,只有本机的客户端可以连接,而其他的客户端无法通过远程连接到Redis服务。这样设置的目的在于,防止Redis服务暴露于危险的网络环境中,被其他机器随意连接。
但是,为了让其他客户端远程连接到服务器端的Redis数据库,读取里面的内容,需要在redis.conf文件中更改这个设置。
以Windows系统为例,使用文本编辑工具打开配置文件,将默认绑定的主机地址更改为bind 0.0.0.0
在配置文件中,找到保护模式的设置部分,将该选项的默认设置protected-mode yes更改为protected-mode no,也就是取消保护模式。
搭建好开发环境,需要先了解Scrapy-Redis的分布式策略,使用这个策略测试是否能远程连接。
分布式策略的实现,需要两部分组成: 配置较好的核心服务器(Master端),配置稍差的爬虫执行端(Slave端)。具体分配情况如下:
在这台计算机上,搭建一个Redis数据库。这台计算机只需负责对URL判重,分配请求,以及存储数据。
这些计算机负责执行爬虫程序,运行过程提交新的请求给Master端。基本实现流程图如下:
图片不可见则点击:https://www.processon.com/view/link/5c9b84e5e4b02cc83504c400
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-H1x6vPR5-1575726189595)(C:\Users\asus\OneDrive\Documents\笔记\爬虫—资料库\分布式爬虫的基本流程图.png)]
基本流程具体如下:
(1) Slave端从Master端拿任务(Request、URL)进行数据爬取,它在爬取数据的同时,会将产生的新任务Request提交给Master端处理
(2)Master端只有一个Redis数据库,负责将未处理的Request去重和任务分配,将处理后的Request加入待爬取队列,并存储爬取的数据。
Scrapy-Redis默认使用该策略,即有一台核心服务器、若干台爬虫端。其中,每个Slave端的功能是一样的,主要负责将网上返回的Item数据和URL提交给Master端管理。
**注意 : **
(1)Slave端提交的请求任务,并不是提交就一定被分配执行,这些任务都会交给Master端进行分配,只要发现某台机器处于空闲,就会为其分配任务。
(2) Scrapy-Redis调度的任务是Request对象,该对象中的信息量(包括URL、回调函数、headers等)比较大,极有可能降低爬虫的速度,并占用大量的存储空间。因此,如果要保证效率,需要一定的硬件水平。
在进行分布式部署前,需要测试是否能够实现远程连接。
Master端按照指定的配置文件启动redis-server。例如:使用命令提示符(管理员)执行如下命令,并读取默认配置:
redis-server redis.windows.conf
启动服务后,为便于检测Slave端是否连接到Redis数据库,可先使用如下命令启动本地的redis-cli:
redis-cli
Slave端如果象连接Master端的数据库,需要在启动时指明Master端的IP地址。例如: 连接的远程计算机IP地址为192.168.199.108
redis-server -h 192.168.199.108
之后可输入已存在的键值名获取数据进行测试。
创建项目指令请参照章节10.3
项目中的setting.py文件中,默认定制各个Scrapy组件的行为。需在此基础上,增加对Scrapy-Redis组件的制定,主要设置内容包括:
DUPERILTER_CLASS用于检测和过滤重复请求的类,默认为scrapy.dupefilters.RFPDupeFilter。这里,必须使用Scrapy-Redis的去重组件,交由Redis数据库执行去重操作。该项目设置示例如下:
DUPEFILTER_CLASS="scrapy_redis.dupefilter.RFPDupeFilter"
SCHEDULER用于爬取网页的调度程序。这里,需使用Scrapy-Redis的调度器,交由Redis分配请求。具体设置示例如下:
SCHEDULER="scrapy_redis.scheduler.Scheduler"
SCHEDULER_PERESIST表示是否在Redis中保存用到的队列。这里无需清理Redis使用的队列,允许项目在执行中暂停和暂停后恢复。此项目设置为:
SCHEDULER_PERESIST=True
ITEM_PIPELINES包含要使用的项目管道及其顺序的字典,默认为空。这里需要将Item数据直接诶保存到Redis数据库中,以供后续的分布式处理这些Item数据,所以可将此项目设置为:
ITEM_PIPELINES={
'scrapy_redis.pipelines.RedisPipeline':100
}
若不想让Item数据保存到Redis数据库中,则可自己编写管道文件,将数据传递到管道中处理。
REDIS_HOST 和 REDIS_PORT表示Redis服务器的主机IP地址和端口,默认读取的是本机的Redis数据库。这里需要指明读取哪个主机的Redis服务。以下是设置样例:
REDIS_HOST='192.168.64.99'
REDIS_PORT=6379
在爬取网页之前,需要明确爬虫的目标网页。具体操作请参照章节10.3
在Scrapy-Redis中,分别提供了两个爬虫类实现爬取网页的操作:RedisSpider、RedisCrawlSpider,它们都位于scrapy_redis.spiders模块中。其中,RedisSpider是Spider的派生类,RedisCrawlSpider是CrawlSpider类的派生类,他们默认已拥有父类中的成员。此外,还定义了自己的若干属性。
redis_key表示Redis数据库从哪里获取起始网址,它是一个队列的名称,相当于start_urls。项目启动是,不会立刻执行爬取操作,而是等待命令。例如:每台Slave端机器都已把项目启动,但都等待redis_key命令,将第一批请求给它们执行,这样做到任务统一调度。
redis_key的一般格式如下:
爬虫名(小写) : start_urls
redis_key示例如下:
redis_key = 'itcast:start_urls'
有时需要将爬取到的URL存放到Redis数据库中,则一般有如下操作:
redis=Redis(host=REDIS_HOST,port=REDIS_PORT,db=REDIS_DB,password=REDIS_PASSWD)
#spider:start_urls是Redis中存放URL的队列名
redis.lpush('spider:start_urls'.self.url+u)
连接到数据库存放URL,这样可保证不断有URL添加,爬虫能一直继续下去。
这个属性即可按照Spider中原有的写法,直接给出爬虫搜索的域名范围,也可动态获取域名。例如:redis_key的值为hr.tencent.com,allowed_domains属性能自动获取该域名,将其作为允许的域名范围。具体设置如下:
def __init__(self,*args,**kwargs):
domain = kwargs.pop('domain','')
self.allowed_domains=filter(None,domain.split(','))
super(当前类名,self).__init__(*args,**kwargs)
两种设置使用哪种都可,任选其一
程序编写完成,就可执行分布式爬虫
首先,需要在Master端启动redis-server。例如:在Windows系统下,进入Redis的安装目录,使用命令提示符执行如下命令:
redis-server redis.windows.conf
将前面创建的项目复制到所有Slave端,打开终端,切换目录至spider目录下,运行爬虫。
注意: 可随机选择任一个Slave端启动,不用区分先后顺序。
此时,所有Slave端计算机均处于等待指令的状态。在Master端的另一个终端中启动redis-cli,之后使用lpush命令推出一个redis_key。具体如下:
lpush itcast:start_urls http://www.itcast.cn/channel/teacher.shtml
爬虫启动,所有Slave端设备开始爬数据,并保存到Redis数据库中。使用可视化工具可看到保存至Redis中的数据。
在Scrapy-Redis项目中,同样可定义多个管道,并让这些管道按照定义的顺序依次处理Item数据。打开创建的pipeline.py文件,其内部还没设置任何内容,在该文件中,定义多个管道类,每个独立的管道类代码如下:
scrapy.exporters提供导出数据的功能,它使用ItemExporter来创建不同的输出格式,如XML、CSV。针对不同的文件格式,Scrapy专门提供相应的内置类分别处理,其中,CsvItemExporter类表示用于输出csv(逗号分隔符)文件格式的读写对象。
要想使用CsvItemExporter类,需要使用如下方法进行实例化:
__init__(self,file,include_headers_line=True,join_multivalued='',**kwargs)
#参数含义如下:
#file: 表示文件
#include_headers_line:启动后,ItemExporter会输出第一行最为列名,列名从BaseItemExporter.fileds_to_export或第一个item fileds获取
#join_multivalued:将用于连接多个值的fields
创建完CsvItemExporter类对象后,必须按照如下3个步骤使用:
(1)调用start_exporting()方法以标识输出过程的开始
(2) 对要导出的每个项目调用export_item()方法
(3)调用finish_exporting()方法以标识输出过程的结束
实例如下:
from scrapy.exporters import CsvItemExporter
class MyspiderCSVpipeline(object):
def open_spider(self,spider):
#创建csv格式的文件
self.file = open('itcast.csv','w')
#创建csv文件读/写对象,将数据写入到指定的文件中
self.csv_exporter = CsvItemExporter(self.file)
#开始执行item数据读写
self.csv_exporter.start_exporting()
def process_item(self,item,spider):
#将item数据写入到文件中
self.csv_exporter.export_item(item)
return item
def close_spider(self,spider):
#结束文件读/写操作
self.csv_exporter.finish_exporting()
#关闭文件
self.file.close()
此管道负责将Item数据写入到Redis数据库中。在Python中,提供用于操作Redis数据库的第三方模块redis。该模块提供两个类:Redis和StrictRedis,用于实现操作Redis数据库的命令。其中,StrictRedis用于实现大部分官方的命令;Redis是StrictRedis的子类,用于向后兼容旧版本的redis-py
在使用Redis之前,需导入模块和创建与Redis数据库的连接:
import redis
redis_cli = redis.Redis(host='192.168.64.99',port=6379)
MyspiderRedisPipeline类实现代码如下:
import redis
import json
class MyspiderRedisPipeline(object):
def open_spider(self,spider):
self.redis_cli = redis.Redis(host='192.168.64.99',port=6379)
def process_item(self,item,spider):
content = json.dump(dict(item),ensure_ascii=False)
self.redis_cli.lpush("Myspider_List",content)
return item
上述定义中,在open_spider()(开始爬虫)方法中,创建了一个Redis数据库的连接redis_cli。在process_item()方法中,调用json模块的dump()函数,将Item数据转换成字符串形式,之后调用lpush()方法将数据写入到指定的Redis数据库中,并以列表的形式存储。
此管道用于将Item数据保存到MongoDB数据库中。基本的创建方法参考章节6.2
示例如下:
class MyspiderMongoPipeline(object):
def open_spider(self,spider):
self.mongo_cli = pymongo.MongoClient(host='127.0.0.1',port=27017)
self.db = self.mongo_cli['itcast']
self.sheet = self.db['itcast']
def process_item(self,item,spider):
self.sheet.insert(dict(item))
return item
在上述代码中,开始爬虫时,创建了一个数据库连接mongo_cli,在创建了一个数据库db和列表sheet。在precess_item()方法中,调用insert()方法将Item数据以字典的形式存入sheet列表中。
打开setting.py 文件,启动上述自定义的这些管道组件,ITEM_PIPELINES配置项的设置乳腺癌:
ITEM_PIPELINES={
'Myspider.pipelines.MyspiderCSVPipeline':200,
'Myspider.pipelines.MyspiderRedisPipeline':300,
'Myspider.pipelines.MyspiderMongoPipeline':400,
'scrapy_redis.pipelines.RedisPipeline':900
}
数值越低,管道的优先级越高,所以上述这些管道的执行顺序是从上而下的。
**注意 :**如果指定了scrapy_redis提供的管道,一般给定的数值会偏大,这样可保证其最后执行。
执行程序,Item数据分别交给由上述的每个管道进行处理,将爬虫数据分别保存到CSV文件、Redis数据库和MongoDB数据库中。
通过分布是爬虫爬取的网页数据,默认会全部保存在Redis数据库中。每次启动Redis库时,都会将本机之前存储的数据加载到内存中。如果数据信息很大,则内存消耗会比较严重。因此,需要将数据从Redis数据库中取出来,另外对其进行持久化存储。
一般来说,数据的持久化存储有两种方式: MongoDB和MySQL。由于Python和MongoDB的交互比较便捷,所以这里选择以MongoDB进行存储。
为了达到这个目的,可以额外编写一个脚本,单独负责将Redis中的数据转移到MongoDB中,这个脚本可跟爬虫同步执行,它不会受到Scrapy框架的影响,只是单纯地从Redis数据库中取数据。
创建的Python脚本具体代码如下:
# -*- conding:utf-8 -*-
import json,redis,pymongo
def main():
#指定Redis数据库信息
redis_cli = redis.Redis(host='192.168.88.94',port=6379,db=0)
#指定MongoDB数据库信息
mongo_cli = pymongo.MongoClient(host='127.0.0.1',port=27017)
#创建数据库名
db = mongo_cli['itcast']
#创建表名
sheet = db['itcast_data']
while True:
#FIFO模式为blpop,LIFO模式为brpop,获取键值
source,data = redis_cli.blpop(["itcast:items"])
item = json.loads(data)
sheet.insert(item)
try:
print u"Processing:%(name) $ <%(link)s>"%item
except KeyError:
print u"Error processing:%r"%item
if __name__ == '__main__':
main()
上述代码中,在main()函数中分别创建了Redis和MongoDB数据库的连接,然后创建数据库和表,接着按照先进先出的模式,使用while循环一直从Redis中取数据。
取到的数据是itcast:items,冒号前是爬虫名。冒号后是爬虫数据,对应着Redis中的数据itcast:items
blpop()方法返回两个值:source和data。其中,source表示爬虫名,data表示Item数据。data是一个字典,需要通过json模块将其转换为Python格式后,插入到MongoDB中。
最后,在程序入口调用main()函数。
在终端使用如下命令启动MongoDB,具体如下:
sudo mongodb
运行上述程序,可看到Redis数据库中的数据已转移到MongoDB中。
值得一提的是,这个脚本和爬虫项目是不冲突的,只要Redis中存入一条数据,就将其取出来插入到MongoDB中。因此,在爬虫启动后,这个脚本会在后台单独运行,无需管它何时结束。直到Redis中无数据才会自动关闭。
在Python中,可使用第三方库fake_useragent生成随机客户代理(User-Agent)
使用方法如下:
from fake_useragent import UserAgent
ua = UserAgent()
print(ua.ie) #随机打印ie浏览器的任意版本
print(ua.chrome)#随即打印chrome浏览器的任意版本
print(ua.firefox)#随机打印firefox浏览器的任意版本
print(ua.random)#随机打印任意浏览器的任意版本
https://docs.python.org/3/index.html
在说动机之前,我想先说说学部分知识的起源。
最开始进实验班的时候,主要考虑到我学东西慢,怕老师讲的不好再碰上自己不爱听,期末挂科怕补不回来,实验班的师资比普通班要好,虽然要求严点,但老师能讲得好,自己费点功夫也能跟上。所以义无反顾跟着舍友一起报了实验班(这里要感谢一下舍友和老软二的成员,感谢146原软二的同学的陪伴)。
进了实验班以后,我依然迷茫。方向已定,但是具体怎么实施我不清楚,直到杨同学和我的一次交流。那次交流,正好有张比较明确的大数据从数据获取到数据结果展示的流程图。也就是这张图,我知道了我要走的步骤。
大数据这条路的第一步,就是数据挖掘(爬虫是一部分)。经过大二上,恰逢寒假,没作业,空余时间比较多。所以寒假前就买好了书,准备开始自学。(这里还要感谢一下我的班导 王绍卿老师,感谢他给我的项目和学习资料)
讲完起源,该说动机了。
寒假快结束的时候,我自己的一本教材总结学完了,但是感觉学的较浅显,所以接着看老师给的教材,这才把笔记二次完善。写作的动机,就是学习和日后回顾。
另外,关于为什么开学近一个月才将笔记公布,后面部分再谈。
(1)爬虫具体怎么学,见仁见智。我所做的只是遵循我手里的资料进行学习,欢迎交流,QQ:460752038
(2)Crawl及以后的部分,因为当时寒假学习的时限,并未完全学完,所以书写的内容相比之前的要多很多。如嫌累赘,可酌情跳读。
(3)笔记成型之长,实属意外。由于返校后更换固态硬盘导致数据丢失,无奈重写资料,月余尚成。
(4)对于每章节的加深巩固,个人持与本人班导相同的思想,即:提供真实案例引导学习。但由于篇幅限制与书写难度,不做该部分内容,若有需要,烦请参考《网络爬虫开发实战》(崔庆才 著)
2019-3-29 SDUT软工1705 Fawkes 于宿舍作