模块一:爬虫基础原理
1.HTTP基本原理
URL、URN和URI
Http和Https
请求
常见请求方法:GET和POST
请求头,常见信息:Host、Cookies、Referer、User-Agent、Content-Type等
请求体:一般承载内容为POST请求中的表单数据,因此对于GET请求,请求体为空
响应
响应头,常见信息:Set-Cookie、Content-Type等
响应体,承载响应的正文数据,在检查-网络-预览中查看。
2.Web网页基础
网页的组成——HTML、CSS、JS
HTML:超文本标记语言(Hyper Text Markup Language)
CSS:层叠样式表(Cascading Style Sheets)
JavaScript
节点树
DOM:文档对象模型(Document Object Model),又称DOM(节点)树,允许程序和脚本动态地访问和更新文档的内容、结构和样式。
节点树与节点的关系
CSS选择器
“.”代表class,“#”代表id……
3. 了解爬⾍的基本原理
获取⽹⻚:
即获取⽹⻚的源代码
提取信息:
分析⽹⻚源代码,提取数据
① 常⽤正则表达式提取,
②也有根据⽹⻚节点属性、CSS选择器或XPath来提取⽹⻚信息的库:BeautifulSoup、pyquery、lxml等。
保存数据:
①可保存为txt或json⽂本
②数据库,如MySQL、MongoDB等,
③还可保存到远程服务器,如借助SFTP进⾏操作。
⾃动化程序:
爬⾍即⼤量、⾼效提取⽹⻚信息的⾃动化程序。
有些⽹⻚回复的是json字符串(API接⼝⼤多采⽤)
JS渲染⻚⾯:
很多⽹⻚采⽤Ajax、前端模块化⼯具构建,
解决办法——分析其后台Ajax接口,也可使用Selenium、Splash这样的库来实现JS渲染。
4. Session与Cookies
静态网页与动态网页
静态网页:存在很大的缺陷,如可维护性差,不能根据URL灵活多变地显示内容等。
动态网页:可能由JSP、PHP、Python等语言编写,还可以实现用户登陆和注册的功能。
无状态HTTP
HTTP协议对事务处理是没有记忆能力的,也就是说服务器不知道客户端是什么状态,如果后续需要处理前面的信息,则必须重传。
Session和Cookies
Session:“会话控制”,存储特定用户会话所需的属性及配置信息。
Cookies:客户端第一次请求服务器时,服务器会返回一个响应头中带有Set-Cookie字段的响应给客户端,然后客户端就会把Cookies保存起来;
浏览器下次再请求该网址时,就会把Cookies放到请求头中提交给服务器。
常见误区
- “只要关闭浏览器,Session就消失了”:除非程序通知服务器删除一个Session,否则服务器会一直保留。
- 当浏览器关闭时,浏览器不会主动在关闭之前通知服务器它将关闭,所以服务器根本不会有机会知道浏览器已经关闭。
5. 了解多线程基础原理
多线程含义
进程是线程的集合,由一个或多个线程构成;
线程是操作系统进行运算调度的最小单位,是进程中的一个最小运行单元
并发与并行
多线程适用场景
IO密集型任务:如爬虫在向服务器发起请求之后,有一段时间必须要等待服务器的响应返回,此类任务即IO密集型任务。
不是所有的任务都是IO密集型任务
计算密集型任务:又称CPU密集型任务,即任务的运行一直需要处理器的参与。
Python实现多线程
python中,实现多线程的模块叫threading。
GIL(Global Interpreter Lock),全局解释器锁;
python多线程操作:获取GIL→执行对应线程的代码→释放GIL
6. 多进程
进程(Process):是具有一定独立功能的程序关于某个数据集合上的一次运行活动,是系统进行资源分配和调度的一个独立单位。
多进程中,每个进程都有属于自己的GIL,所以在多核处理器下,多进程的运行不会受到GIL影响,多进程能更好地发挥多核的优势。
多进程的实现
1)直接使用Process类
常用方法:cpu_count方法来获取当前机器CPU的核心数量;active_children方法来获取当前还在运行的所有进程。
2)继承Process类:通过继承的方法创建一个进程类,进程的基本操作在子类的run方法中实现即可。
3)守护进程:可以设置daemon属性来控制是否为守护进程。
4)进程等待:让所有子进程都执行完了然后再结束,只需要加入join方法即可。
5)终止进程:通过terminate方法来终止某个子进程,还可以通过is_alive方法判断进程是否还在运行。
进程互斥锁
使用Lock类
信号量
信号量可以控制临近资源的数量,实现多个进程同时访问共享资源,限制进程的并发量,使用Semaphore类来实现信号量
队列
让进程共享数据,即Queue类
管道
可以理解为两个进程之间通信的通道
可以是单向的,即half-duplex:一个进程负责发消息,一个进程负责接收信息
也可以是双向的duplex,即互相收发信息,默认声明Pipe对象是双向管道
进程池
即Pool类,可以提供指定数量的进程,供用户调用
map方法的使用:第一个参数就是要启动的进程对应的执行方法,第二个参数是一个可迭代对象,其中的每个元素会被传递给这个执行方法。
模块二:爬虫基本库的使用
7. Requests库的使用
请求
- GET请求
import requests
r = requests.get('http://xxxx/')
print(r.text)
- 抓取网页
import requests
import re
r = requests.get('http://xxxx/')
pattern = re.compile('<h2.*?>(.*?)</h2>',re.S)
title = re.findall(pattern,r.text)
print(titles)
- 抓取二进制数据
import requests
r = requests.get('http://xxxx/')
with open('xxx.ico','wb')as f:
f.write(r.content)
- 添加headers
- Post请求
import requests
data = {'name':'germey','age':'25'}
r = requests.get('http://xxxx/',data=data)
print(r.text)
- 响应(Response)
import requests
r = requests.get('http://xxxx/')
print(type(r.status_code),r.status_code)
print(type(r.headers),r.headers)
print(type(r.cookies),r.cookies)
print(type(r.url),r.url)
print(type(r.history),r.history)
高级用法
- 文件上传
import requests
files = {'file':open('xxx.ico','rb'}
r = requests.post('http://xxxx/post',files=files)
print(r.text)
- Cookies
import requests
r = requests.get('http://xxxx')
print(r.cookies)
for key,value in r.cookies.items():
print(key + '=' + value)
headers中加入Cookies,来保持登录状态:
构造一个RequestsCookieJar对象,将刚才复制的Cookie处理下并赋值:
-
Sessions 维持:用get或post方法多次获取网页信息,相当于多次打开浏览器打开不同网页,这时维持同一个Session就相当于打开不同的标签页
-
SSL验证:有些网站没有或SSL证书不被机构认可,这时候可以通过设置verify参数为False来避开证书验证。可以设置忽略警告,或者捕获警告到日志,或指定一个本地证书,可以是一个文件或两个文件路径组成的元祖
-
超时设置:可以设置timeout参数来限定时间,该参数默认值为none
-
身份认证:通过设置auth参数设置账户和密码;
或使用Oauth(第三方包)认证
-
代理设置:有些网站会在大规模频繁请求时出现验证码等情况,这时可设置代理来解决,使用的是proxies参数;
此外还有socks代理(第三方库)
-
Prepared Request
8. 正则表达式(Regular Expression)
开源中国的正则测试工具:http://tool.oschina.net/regex/
- url的组成格式为:协议类型+冒号双斜线+域名和路径
故url的正则表达式为:
[a-zA-z]+://[^\s]*
模式 | 描述 |
---|---|
^ | 匹配字符串的开头 |
$ | 匹配字符串的末尾。 |
. | 匹配任意字符,除了换行符,当re.DOTALL标记被指定时,则可以匹配包括换行符的任意字符。 |
[…] | 用来表示一组字符,单独列出:[amk] 匹配 ‘a’,‘m’或’k’ |
[^…] | 不在[]中的字符:[^abc] 匹配除了a,b,c之外的字符。 |
re* | 匹配0个或多个的表达式。 |
re+ | 匹配1个或多个的表达式。 |
re? | 匹配0个或1个由前面的正则表达式定义的片段,非贪婪方式 |
re{ n} | 精确匹配 n 个前面表达式。例如, o{2} 不能匹配 “Bob” 中的 “o”,但是能匹配 “food” 中的两个 o。 |
re{ n,} | 匹配 n 个前面表达式。例如, o{2,} 不能匹配"Bob"中的"o",但能匹配 "foooood"中的所有 o。“o{1,}” 等价于 “o+”。“o{0,}” 则等价于 “o*”。 |
re{ n, m} | 匹配 n 到 m 次由前面的正则表达式定义的片段,贪婪方式 |
a| b | 匹配a或b |
(re) | 对正则表达式分组并记住匹配的文本 |
(?imx) | 正则表达式包含三种可选标志:i, m, 或 x 。只影响括号中的区域。 |
(?-imx) | 正则表达式关闭 i, m, 或 x 可选标志。只影响括号中的区域。 |
(?: re) | 类似 (…), 但是不表示一个组 |
(?imx: re) | 在括号中使用i, m, 或 x 可选标志 |
(?-imx: re) | 在括号中不使用i, m, 或 x 可选标志 |
(?#…) | 注释. |
(?= re) | 前向肯定界定符。如果所含正则表达式,以 … 表示,在当前位置成功匹配时成功,否则失败。但一旦所含表达式已经尝试,匹配引擎根本没有提高;模式的剩余部分还要尝试界定符的右边。 |
(?! re) | 前向否定界定符。与肯定界定符相反;当所含表达式不能在字符串当前位置匹配时成功 |
(?> re) | 匹配的独立模式,省去回溯。 |
\w | 匹配字母数字及下划线 |
\W | 匹配非字母数字及下划线 |
\s | 匹配任意空白字符,等价于 [ \t\n\r\f]。 |
\S | 匹配任意非空字符 |
\d | 匹配任意数字,等价于 [0-9]. |
\D | 匹配任意非数字 |
\A | 匹配字符串开始 |
\Z | 匹配字符串结束,如果是存在换行,只匹配到换行前的结束字符串。 |
\z | 匹配字符串结束 |
\G | 匹配最后匹配完成的位置。 |
\b | 匹配一个单词边界,也就是指单词和空格间的位置。例如, ‘er\b’ 可以匹配"never" 中的 ‘er’,但不能匹配 “verb” 中的 ‘er’。 |
\B | 匹配非单词边界。‘er\B’ 能匹配 “verb” 中的 ‘er’,但不能匹配 “never” 中的 ‘er’。 |
\n, \t, 等. | 匹配一个换行符。匹配一个制表符。等 |
\1…\9 | 匹配第n个分组的内容。 |
\10 | 匹配第n个分组的内容,如果它经匹配。否则指的是八进制字符码的表达式。 |
-
使用match判断正则表达式是否能匹配到字符串,输出结果是SRE-match对象,它有两个方法:group输出匹配的内容,span输出匹配的范围(但match方法都是从文本开头进行匹配,并不适合匹配字符串)
-
从文段中提取字符串内容,使用()将提取的子字符串括起来,然后用group方法传入分组的索引即可提取
-
.*可以匹配任意字符无限次(.代表任意字符,.代表无限次)
-
.*为贪婪匹配,可能会吃点部分字符,而.*?为非贪婪匹配
-
正则可以添加修饰符来控制匹配的模式,例如re.S可用来匹配带有换行符的内容
修饰符 | 描述 |
---|---|
re.I | 使匹配对大小写不敏感 |
re.L | 做本地化识别(locale-aware)匹配 |
re.M | 多行匹配,影响^和$ |
re.S | 使匹配包括换行在内的所有字符 |
re.U | 根据Unicode字符集解析字符。这个标志影响\w、\W、\b和\B |
re.X | 该标志通过给予你更灵活的格式以便你将正则表达式写的更易于理解 |
-
当文本中含有特殊字符时,使用转义字符来匹配
-
search方法:扫描整个文本,并返回第一个匹配的结果
-
findall方法:会搜索整个字符串,然后返回匹配到的所有结果
-
sub方法:可以替换字符串中的内容,常用以去除文本中的某些内容(使用空字符串“”来替换)
-
compile方法:可以将正则字符串编译成正则表达式对象,可在后面的匹配中复用
9. PyQuery
PyQuery可以直接解析DOM节点的结构和属性来提取信息
初始化
-
字符串初始化
解析html,需要先将html文本初始化为一个pyquery对象
-
URL初始化
-
文件初始化,设置filename参数即可
-
基本CSS选择器,遍历节点,调用text方法,即可提取出文本内容
html = '''
<div class="clear t_center nav">
<ul id="zMenu">
<li class="current"><a href="/">首页</a></li>
<li><a href="/Home/InfoList?str=1">养猪资讯</a></li>
<li style="width:90px;"><a href="http://zjs.nxin.com" target="_blank">国家生猪市场</a></li>
<li><a href="http://hqb.nxin.com" target="_blank">行情宝</a></li>
<li><a href="https://www.nxin.com/classroom.html" target="_blank">养猪课堂</a></li>
<li><a href="http://120.nxin.com" target="_blank">猪病通</a></li>
</ul>
</div>
'''
doc = pq(html)
print(doc('.clear.t_center.nav #zMenu li'))
for item in doc('.clear.t_center.nav #zMenu li').items():
print(item.text())
当class中含有空格时,说明它是含有两个或以上的class,因此进行选择的时候,将多个div标签都选择出来
常用查询方法
- 子节点:使用find方法,find会查找出对象的所有子孙节点,
如果只想要子节点,可以使用children方法,括号中传入CSS选择器,还可以筛选节点
-
父节点:可以用parent方法,得到直接父节点,不会得到更高节点。
想要获取祖先节点,可以使用parents方法
-
兄弟节点:使用siblings方法
-
遍历:对于需要获取的多个节点,使用遍历,需要先调用items方法
获取信息
- 获取属性:调用attr方法(只传第一个参数时),该方法会返回第一个匹配的节点属性,因此需要多个节点属性时,需要配合items方法进行遍历
- 获取文本:调用text方法,该方法只返回字符串内容,它可以直接获取多个节点文本;
需要其中的html部分内容时,则调用html方法,它只能获取第一个节点的内容,多个节点时,需要遍历
节点操作
-
addclass和removeclass方法
-
attr(传两个参数时)、text和html方法(传参时)
-
remove方法
html = '''
<div class="wrap">
Hello,World
<p>This is a paragragh.</p>
<div>
'''
from pyquery import PyQuery as pq
doc = pq(html)
wrap = doc('.wrap')
print(wrap.text())
wrap.find('p').remove()
print(wrap.text())
- 其他节点操作方法:如append、empty、prepend等方法
10. 高效存储MongoDB
MongoDB:是由C++语言编写的非关系型数据库,是一个基于分布式文件存储的开源数据库系统,其内容存储形式类似JSON对象,它的字段值可以包含其他文档、数组及文档数组
安装MongoDB、PyMongo
-
MongoDB安装,默认在本地localhost的27017端口上,安装步骤:https://www.cnblogs.com/TM0831/p/10606624.html
-
安装PyMongo库
-
连接MongoDB:使用MongoClient
import pymongo
client = pymongo.MongoClient(host=“localhost”,port=27017)
它的host参数还可以直接传入MongoDB 的连接字符串
client = MogoClient(‘mongodb://localhost:27017/)
指定数据库及集合
- 指定数据库
db = client.test
db = client[‘test’]
- 指定集合
collection = db.students
collection = db[‘students’]
插入数据
使用insert_1和insert_many方法
student1 ={
'id':'20170101',
'name':'Jordan',
'age':20,
'gender':'male'
}
student2 = {
'id':'20170102',
'name':'Adam',
'age':21,
'gender':'male'
}
result = collection.insert_one(student1)
print(result)
print(result.inserted_id)
results = collection.insert_many([student1,student2])
print(results)
print(results.inserted_ids)
查询
- 使用find_one查询单个对象
result = collection.find_one({'name': 'Mike'})
print(type(result))
print(result)
也可通过ObjectId属性来查询
- 使用find查询多个对象,需要使用遍历
results = collection.find({'age':20})
print(results)
for result in results:
print(result)
- 根据比较条件查询结果,使用比较符号
results = collection.find({'age':{'$gt':20}})
for result in results:
print(result)
- 也可使用正则表达式等方式查询对象,常用功能符号如下
计数
使用count方法计数符合条件得数据条数
count = collection.find({'age':20}).count()
print(count)
排序
使用sort方法
results = collection.find().sort('name',pymongo.ASCENDING)
print([result['name']for result in results])
偏移
- 使用skip方法偏移几个位置
results = collection.find().sort('name',pymongo.ASCENDING).skip(1)
print([result['name'] for result in results])
- 使用limit方法指定要提取的个数
results = collection.find().sort('name',pymongo.ASCENDING).skip(1).limit(1)
print([result['name'] for result in results])
更新
使用update_one方法和update_many方法,指定更新的条件和更新后的数据;
可以使用$set操作符对数据更新
condition = {'name':'Wales'}
student = collection.find_one(condition)
student['gender'] = 'female'
result = collection.update_one(condition,{'$set':student})
print(result)
print(result.matched_count,result.modified_count)
#输出
<pymongo.results.UpdateResult object at 0x00000280D11BD940>
1 0
删除
- 使用remove方法(不推荐)
result = collection.remove({'name':'Wales'})
print(result)
#输出
{'n': 1, 'ok': 1.0}
- 使用delete_one和delete_many方法
result = collection.delete_one({'name':'Wales'})
print(result)
#输出
<pymongo.results.DeleteResult object at 0x00000280D1E5C0C0>
1
result = collection.delete_many({'age':{'$lt':21}})
print(result)
print(result.deleted_count)
#输出
<pymongo.results.DeleteResult object at 0x00000280D15AC5C0>
1
其他操作
11. Requsts+Pyquery+MongoDB实战
爬取思路
- 用requests爬取这个站点每一页的电影列表顺着列表再爬取每个电影的详情页
- 用pyquery和正则表达式提取每部电影的名称、封面、类别、上映时间、评分、剧情简介等
- 把以上爬取的内容存入Mongo DB数据库
- 使用多进程实现爬取的加速
爬取列表页
- 遍历页码构造10页的索引页URL
- 从每个索引页分析提取出每个电影的详情页URL