爬虫
什么是爬虫?
网络爬虫(又被称为网页蜘蛛,网络机器人)就是模拟客户端发送网络请求,接收请求响应,
一种按照一定的规则,自动地抓取互联网信息的程序。
原则上,只要是浏览器(客户端)能做的事情,爬虫都能够做
爬虫的分类:
通用爬虫 :通常指搜索引擎的爬虫
聚焦爬虫 :针对特定网站的爬虫(重点)
聚焦爬虫的具体流程:
构造url
发起请求获取响应
提取数据
保存数据
搜索引擎的工作流程:
抓取网页
数据存储
预处理
提供检索服务网站排名
搜索引擎的局限性:
通用搜索引擎所返回的网页里80%的内容无用
图片、音频、视频多媒体的内容通用搜索引擎无能为力
不同用户搜索的目的不全相同,但是返回内容相同
robots协议
网站通过robots协议告诉搜索引擎那些数据可以搜索,那些不能抓取,但仅仅只是道德方面的约束
具体如下:
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
Allow: /ershou
Allow: /$
Disallow: /product/
Disallow: /
User-Agent: Googlebot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Allow: /ershou
Allow: /$
Disallow: /
User-agent: Bingbot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Allow: /ershou
Allow: /$
Disallow: /
User-Agent: 360Spider
Allow: /article
Allow: /oshtml
Allow: /ershou
Disallow: /
User-Agent: Yisouspider
Allow: /article
Allow: /oshtml
Allow: /ershou
Disallow: /
User-Agent: Sogouspider
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /ershou
Disallow: /
User-Agent: Yahoo! Slurp
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Allow: /ershou
Allow: /$
Disallow: /
User-Agent: *
Disallow: /
http和https的区别
端口不同(http:80,https:443)
安全程度不同,https更安全些
浏览器发送http/https的请求过程:
先找到DNS服务器解析域名,在找对应的服务器
url的形式
url的形式:scheme://host[:port#]/path/…/[?query-string][#anchor]
scheme:协议(例如:http, https, ftp)
host:服务器的IP地址或者域名
port:服务器的端口(如果是走协议默认端口,80 or 443)
path:访问资源的路径
query-string:参数,发送给http服务器的数据
anchor:锚(跳转到网页的指定锚点位置)
http://localhost:4000/file/part01/1.2.html
http://item.jd.com/11936238.html#product-detail
url地址中是否包含锚点对响应没有影响
HTTP常见请求头
Host (主机和端口号)
Connection (链接类型)
Upgrade-Insecure-Requests (升级为HTTPS请求)
User-Agent (浏览器名称)
Accept (传输文件类型)
Referer (页面跳转处)
Accept-Encoding(文件编解码格式)
Cookie (Cookie)
x-requested-with :XMLHttpRequest (是Ajax 异步请求)
post和get请求的区别
get提交数据时,受到提交数据量的影响,而post并没有限制
get请求是将数据放在url中传递,而post请求时将数据放在请求体中来传递
get能被缓存而post不能被缓存
get只允许asill码而post并没有过多的限制
常见的响应状态码:
200:成功
302:临时转移至新的url
307:临时转移至新的url
404:not found
403: 人家不想给你数据
500:服务器内部错误
Unicode UTF8 ASCII的区别:
字符(Character)是各种文字和符号的总称,包括各国家文字、标点符号、图形符号、数字等
字符集(Character set)是多个字符的集合
字符集包括:ASCII字符集、GB2312字符集、GB18030字符集、Unicode字符集等
ASCII编码是1个字节,而Unicode编码通常是2个字节。
UTF-8是Unicode的实现方式之一,UTF-8是它是一种变长的编码方式,可以是1,2,3个字节
python3中两种字符串类型:
str : unicode的呈现形式
bytes :字节类型,互联网上数据的都是以二进制的方式(字节类型)传输的
使用方法:
str 使用encode方法转化为 bytes
bytes 通过decode转化为 str
编码方式解码方式必须一样,否则就会出现乱码
python2中字符串有两种类型
unicode类型
字节类型
在Python2中,字符串无法完全地支持国际字符集和Unicode编码。为了解决这种限制,
Python2对Unicode数据使用了单独的字符串类型。要输入Unicode字符串字面量,要在第一个引号
前加上'u'。
Python2中普通字符串实际上就是已经编码(非Unicode)的字节字符串。
request模块的使用
为什么一般用request的比较多?
requests的底层实现就是urllib
requests在python2 和python3中通用,方法完全一样
requests简单易用
Requests能够自动帮助我们解压(gzip压缩的等)网页内容
作用:
发送网络请求,返回响应数据
response = requests.get(url)后
response的常用属性:
response.text
respones.content
response.status_code
response.request.headers
response.headers
response.text 和response.content的区别
response.text
类型:str
解码类型: 根据HTTP 头部对响应的编码作出有根据的推测,推测的文本编码
如何修改编码方式:response.encoding="gbk"
response.content
类型:bytes
解码类型: 没有指定
如何修改编码方式:response.content.deocde("utf8")
获取网页源码的通用三种方式:
response.content.decode()
response.content.decode("GBK")
response.text
request模块的深入使用
使用requests模块发送post请求
用法: response = requests.post(url=url,data = data,headers=headers)
data 的形式:字典
使用代理IP技术
为什么要使用代理
让服务器以为不是同一个客户端在请求
防止我们的真实地址被泄露,防止被追究
代理的分类
正向代理
已经知道服务器的IP地址时,发出的请求
反向代理
浏览器不知道服务器的真实地址,如:nginx
代理的使用
常用的代理网站:
https://proxy.mimvp.com/
https://www.kuaidaili.com/
http://www.goubanjia.com/
http://www.ip3366.net/
使用方法一:
用法: requests.get("http://www.baidu.com", proxies = proxies)
proxies的形式:字典
例如:
proxies = {
"http": "http://12.34.56.79:9527",
"https": "https://12.34.56.79:9527",
}
使用代理二:
代理池技术
import requests
#导入random,对ip池随机筛选
import random
proxy = [
{
'http': 'http://61.135.217.7:80',
'https': 'http://61.135.217.7:80',
},
{
'http': 'http://118.114.77.47:8080',
'https': 'http://118.114.77.47:8080',
},
{
'http': 'http://112.114.31.177:808',
'https': 'http://112.114.31.177:808',
},
{
'http': 'http://183.159.92.117:18118',
'https': 'http://183.159.92.117:18118',
},
{
'http': 'http://110.73.10.186:8123',
'https': 'http://110.73.10.186:8123',
},
]
url = '爬取链接地址'
response = requests.get(url,proxies=random.choice(proxy))
代理IP的分类
根据代理服务器端的配置,向目标地址发送请求时,REMOTE_ADDR, HTTP_VIA,HTTP_X_FORWARDED_FOR三个变量不同而可以分为下面四类:
透明代理(Transparent Proxy)
REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Your IP
透明代理虽然可以直接“隐藏”你的IP地址,但是还是可以从HTTP_X_FORWARDED_FOR来查到你是谁。
匿名代理(Anonymous Proxy)
REMOTE_ADDR = proxy IP
HTTP_VIA = proxy IP
HTTP_X_FORWARDED_FOR = proxy IP
匿名代理比透明代理进步了一点:别人只能知道你用了代理,无法知道你是谁。
混淆代理(Distorting Proxies)
REMOTE_ADDR = Proxy IP
HTTP_VIA = Proxy IP
HTTP_X_FORWARDED_FOR = Random IP address
如上,与匿名代理相同,如果使用了混淆代理,别人还是能知道你在用代理,但是会得到
一个假的IP地址,伪装的更逼真
高匿代理(Elite proxy或High Anonymity Proxy)
REMOTE_ADDR = Proxy IP
HTTP_VIA = not determined
HTTP_X_FORWARDED_FOR = not determined
可以看出来,高匿代理让别人根本无法发现你是在用代理,所以是最好的选择。
从使用的协议:代理ip可以分为http代理,https代理,socket代理等,使用的时候需要根据
抓取网站的协议来选择
代理IP使用的注意点
反反爬
使用代理ip是非常必要的一种反反爬的方式,但是即使使用了代理ip,对方服务器任然会
有很多的方式来检测我们是否是一个爬虫
比如:
一段时间内,检测IP访问的频率,访问太多频繁会屏蔽
检查Cookie,User-Agent,Referer等header参数,若没有则屏蔽
服务方购买所有代理提供商,加入到反爬虫数据库里,若检测是代理则屏蔽
所以更好的方式是购买质量更高的代理,或者自己搭建代理服务器,组装自己的代理IP池,
同时在使用的时候使用随机的方式进行选择使用,不要每次都用一个代理ip,没事没有
任何效果的,
代理ip池的更新,
购买的代理ip很多时候大部分(超过60%)可能都没办法使用,这个时候就需要通过程序
去检测哪些可用,把不能用的删除掉。对应的实现方式在我们学习了超时参数的使用
之后大家会了解
requess模块处理cookie相关的请求
cookie和session的区别:
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能。
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie
爬虫中为什么要使用cookie
带上cookie的好处
能够访问登录后的页面
正常的浏览器在请求服务器的时候肯定会带上cookie(第一次请求某个地址除外),
所以对方服务器有可能会通过是否携带cookie来判断我们是否是一个爬虫,对应的
能够起到一定的反爬的效果
带上cookie的坏处
一套cookie往往对应的是一个用户的信息,请求太频繁有更大的可能性被对方识别为爬虫
那么上面的问题如何解决 ?使用多个账号
requests处理cookie相关的请求之session
requests 提供了一个叫做session类,来实现客户端和服务端的会话保持
会话保持有两个内涵:
保存cookie
实现和服务端的长连接
使用方法
session = requests.session()
response = session.get(url,headers)
session实例在请求了一个网站后,对方服务器设置在本地的cookie会保存在session中,
下一次再使用session请求对方服务器的时候,会带上前一次的cookie
requests处理cookie相关的请求之cookie放在headers中
注意:
headers中的cookie:
使用分号(;)隔开
分号两边的类似a=b形式的表示一条cookie
a=b中,a表示键(name),b表示值(value)
在headers中仅仅使用了cookie的name和value
在headers中使用cookie
headers = {
"User-Agent":"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_13_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36",
"Cookie":" Pycharm-26c2d973=dbb9b300-2483-478f-9f5a-16ca4580177e; Hm_lvt_98b9d8c2fd6608d564bf2ac2ae642948=1512607763; Pycharm-26c2d974=f645329f-338e-486c-82c2-29e2a0205c74; _xsrf=2|d1a3d8ea|c5b07851cbce048bd5453846445de19d|1522379036"}
requests.get(url,headers=headers)
cookie有过期时间,所以直接复制浏览器中的cookie可能意味着下一程序继续运行的时候
需要替换代码中的cookie,对应的我们也可以通过一个程序专门来获取cookie供其他
程序使用;当然也有很多网站的cookie过期时间很长,这种情况下,直接复制cookie来使用
更加简单
requests处理cookie相关的请求之使用cookies参数
cookies的形式:字典
cookies = {"cookie的name":"cookie的value"}
使用方法:
requests.get(url,headers=headers,cookies=cookie_dict}
js的执行
用requests模块来获取cookie
requests.utils.dict_from_cookiejar:把cookiejar对象转化为字典
import requests
url = "http://www.baidu.com"
response = requests.get(url)
print(type(response.cookies))
cookies = requests.utils.dict_from_cookiejar(response.cookies)
print(cookies)
输出为:
<class 'requests.cookies.RequestsCookieJar'>
{'BDORZ': '27315'}
用requests模块来处理证书的不安全性
import requests
url = "https://hao123.com"
# 可以使用当前网站
response = requests.get(url,verify=False)
超时参数的使用
使用方法如下:
response = requests.get(url,timeout=3)
通过添加timeout参数,能够保证在3秒钟内返回响应,否则会报错
retrying模块的使用
用retrying模块提供的retry模块
通过装饰器的方式使用,让被装饰的函数反复执行
retry中可以传入参数stop_max_attempt_number,让函数报错后继续重新执行,
达到最大执行次数的上限,如果每次都报错,整个函数报错,如果中间有一个成功,
程序继续往后执行
代码如下:
import requests
from retrying import retry
headers = {}
@retry(stop_max_attempt_number=3) #最大重试3次,3次全部报错,才会报错
def _parse_url(url)
response = requests.get(url, headers=headers, timeout=3) #超时的时候回报错并重试
assert response.status_code == 200 #状态码不是200,也会报错并充实
return response
def parse_url(url)
try: #进行异常捕获
response = _parse_url(url)
except Exception as e:
print(e)
response = None
return response
数据提取的概念和方式
什么是数据提取
简单的来说,数据提取就是从响应中获取我们想要的数据的过程
爬虫中数据的分类
先去看网页返回的数据类型是什么样的
如果网页的标题中的文本在网页的源代码中一般是属于非结构化数据
结构化数据:json,xml等
处理方式:直接转化为python类型
非结构化数据:HTML
处理方式:正则表达式、xpath
XML数据:
<bookstore>
<book category="COOKING">
<title lang="en">Everyday Italian</title>
<author>Giada De Laurentiis</author>
<year>2005</year>
<price>30.00</price>
</book>
<book category="CHILDREN">
<title lang="en">Harry Potter</title>
<author>J K. Rowling</author>
<year>2005</year>
<price>29.99</price>
</book>
<book category="WEB">
<title lang="en">Learning XML</title>
<author>Erik T. Ray</author>
<year>2003</year>
<price>39.95</price>
</book>
</bookstore>
数据提取之JSON
JSON(JavaScript Object Notation) 是一种轻量级的数据交换格式,它使得人们很容易的进行阅读和编写。同时也方便了机器进行
解析和生成。适用于进行数据交互的场景,比如网站前台与后台之间的数据交互。
url中含有jsonp的字符串
可以直接去除url中的jsonp({xxxx}),这样做的好处是直接返回的是json的数据格式
;jsonp1({"count": 18, "start": 0, "subject_collection_items": "...."})
其中jsonp1似乎是很眼熟,在请求的地址中包含一个参数callback=jsonp1,
正是由于这个参数的存在,才导致结果中也会有这部分数据,对应的解决方法是:
直接删除url地址中的callback字段即可,在url地址中很多字段都是没用的,
比如这里的&loc_id,_等等,可以大胆尝试
我们如何确定数据在哪里
在url地址对应的响应中搜索关键字即可
但是注意:url地址对应的响应中,中文往往是被编码之后的内容,
所以更推荐大家去搜索英文和数字;另外一个方法就是在perview中搜索,
其中的内容都是转码之后的
切换手机版寻找返回json的地址
在chrome中点击切换手机版的选项,需要重新刷新页面才能够切换成功,
部分网站还需要重新进入主页面之后再继续点击才能够切换成功,比如:豆瓣热映
现在我们找到了返回电影数据的地址:https://m.douban.com/rexxar/api/v2/subject_collection/movie_showing/items?os=android&for_mobile=1&callback=jsonp1&start=0&count=18&loc_id=108288&_=1524495777522
通过请求我们发现,并不能成功,那是为什么呢?
对比抓包的地址和现在的地址,会发现部分headers字段没有,通过尝试,
会发现是Referer字段缺少的原因
JSON的数据转换方式
loads 将字符串转换为Python的数据类型
load 将包含json的类文件对象转换为Python的数据类型
dumps 将Python的数据类型专换为字符串
dump 将Python的数据类型转换为包含json的类文件对象
其中类文件对象的理解:
具有read()或者write()方法的对象就是类文件对象,比如
f = open(“a.txt”,”r”) f就是类文件对象
具体使用方法:
#json.dumps 实现python类型转化为json字符串
#indent实现换行和空格
#ensure_ascii=False实现让中文写入的时候保持为中文
json_str = json.dumps(mydict,indent=2,ensure_ascii=False)
#json.loads 实现json字符串转化为python类型
my_dict = json.loads(json_str)
#json.dump 实现把python类型写入类文件对象
with open("temp.txt","w") as f:
json.dump(mydict,f,ensure_ascii=False,indent=2)
# json.load 实现类文件对象中的json字符串转化为python类型
with open("temp.txt","r") as f:
my_dict = json.load(f)
数据提取之正则
- 正则的语法
- . 匹配到出了
之外的所有字符,re.S模式下可以匹配
- 转义
- [] 或,选择其中的一个字符
- | 或,选择|两边的内容
- * 匹配0次或者多次
- + 匹配一次或者多次
- ? 费贪婪
- s 空白字符,包含空格,
,
- d 数字
- re模块的常用方法
- re.findall("regex","str") #返回列表
- re.sub("regex","_","str") #返回字符串
- re.compile("regex",re.S) # 编译,提高匹配效率
- 原始字符串r
- 相对于特殊符号而言,表示特殊符号的字面意思
- 用途
- 正则:忽略转义符号带来的印象,加上r之后照着写
- windows下文件路径
数据提取之xpath和lxml
xpath语法:
- // 的用途
- //a 当前html页面上的所有的a
- bookstore//book bookstore下的所有的book元素
- @ 的使用
- //a/@href 所有的a的href
- //title[@lang="eng"] 选择lang=eng的title标签
- text() 的使用
- //a/text() 获取所有的a下的文本
- //a[text()='下一页'] 获取文本为下一页的a标签
- a//text() a下的所有的文本
- xpath查找特定的节点
- //a[1] 选择第一个
- //a[last()] 最后一个
- //a[position()<4] 前三个
具体用法如下:
items["username"] = div.xpath('.//h2/text()')[0]
items["age"] = div.xpath('.//div[@class="articleGender womenIcon"]/text()')[0] if len(div.xpath('.//div[@class="articleGender womenIcon"]/text()'))>0 else None
items["text"] = div.xpath('.//div[@class="content"]/span/text()')
items["laugh"] = div.xpath('.//span[@class="stats-vote"]/i/text()')[0]
items["comment"] = div.xpath('.//div[@class="main-text"]/text()')
items["comment_author"] =
div.xpath('.//div[@class="cmtMain"]//span[@class="cmt-name"]/text()')
安装lxml库
pip install lxml
from lxml import etree
element = etree.HTML(html_str) #bytes或者str类型的字符串
element.xpath("xpath str") #返回列表
etree.tostring(element) #转化为字符串
#数据提取时:先分组,在提取,要不然数据对不齐,保存时会错位,会出现比较严重的问题
提高爬虫的效率
多线程
多进程
协程
线程池
进程池
队列的使用
from queue import Queue
q = Queue(maxsize=100)
item = {}
q.put_nowait(item) #不等待直接放,队列满的时候会报错
q.put(item) #放入数据,队列满的时候回等待
q.get_nowait() #不等待直接取,队列空的时候会报错
q.get() #取出数据,队列为空的时候会等待
q.qsize() #获取队列中现存数据的个数
q.join() #队列中维持了一个计数,计数不为0时候让主线程阻塞等待,队列计数为0的时候才会继续往后执行
q.task_done() #put的时候计数+1,get不会-1,get需要和task_done 一起使用才会-1from queue import Queue