62.1-爬虫概述、Robots协议
好的生活就是不瞎想,做得多,要得少,常微笑懂知足!
总结:
- HTTP 是基于 socket 通讯的;是 异步请求;
- data参数决定是GET还是POST请求:为空采用GET方法;不为空采用POST方法;
- 对URL打包,对数据进行解析;
1. 概述
当今大数据的时代,网络爬虫已经成为了获取数据的一个重要手段。
爬虫,应该称为网络爬虫,也叫网页蜘蛛、网络机器人、网络蚂蚁等。
搜索引擎,就是网络爬虫的应用者。
为什么到了今天,反而这个词汇被频繁的提起呢?有搜索引擎不就够了吗?
实际上,大数据时代的到了,所有的企业都希望通过海量数据发现其中的价值。
所以,需要爬取对特定网站、特定类别的数据,而搜索引擎不能提供这样的功能,因此,需要自己开发爬虫来解
决。
2. 爬虫分类
通用爬虫
常见就是搜索引擎,无差别的收集数据、存储,提取关键字,构建索引库,给用户提供搜索接口。
爬取一般流程
1.初始一批URL, 将这些URL放到待爬取队列
2.从队列取出这些URL, 通过DNS解析IP, 对IP对应的站点下载HTML页面, 保存到本地服务器中, 爬取完的
URL放到已爬取队列。
3.分析这些网页内容, 找出网页里面的其他关心的URL链接, 继续执行第2步, 直到爬取条件结束。搜索引擎如何获取一个新网站的URL
● 新网站主动提交给搜索引擎
● 通过其它网站页面中设置的外链
● 搜索引擎和DNS服务商合作, 获取最新收录的网站
聚焦爬虫
有针对性的编写特定领域数据的爬取程序,针对某些类别数据采集的爬虫,是面向主题的爬虫
3. Robots协议(原则)
指定一个robots.txt文件,告诉爬虫引擎什么可以爬取。
/ 表示网站根目录,表示网站所有目录。
Allow 允许爬取的目录
Disallow 禁止爬取的目录
可以使用通配符
淘宝 http://www.taobao.com/robots.txt
马蜂窝 http://www.mafengwo.cn/robots.txt
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
Allow: /ershou
Disallow: /product/
Disallow: /
User-Agent: Googlebot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Allow: /ershou
Disallow: /
User-agent: Bingbot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Allow: /ershou
Disallow: /
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
Allow: /ershou
Disallow: /product/
Disallow: /
其它爬虫,不允许爬取
User-Agent: *
Disallow: /
这是一个君子协定,“爬亦有道”
这个协议为了让搜索引擎更有效率搜索自己内容,提供了如Sitemap这样的文件。Sitemap往往是一个XML文件,
提供了网站想让大家爬取的内容的更新信息。
这个文件禁止抓取的往往又是可能我们感兴趣的内容,它反而泄露了这些地址。
301,302
301,302 都是HTTP状态的编码,都代表着某个URL发生了转移,不同之处在于:
301 redirect: 301 代表永久性转移(Permanently Moved)。
302 redirect: 302 代表暂时性转移(Temporarily Moved )。
4. HTTP请求和响应处理
其实爬取网页就是通过HTTP协议访问网页,不过通过浏览器访问往往是人的行为,把这种行为变成使用程序来访问。
4.1 urllib包
urllib包(最老的爬虫库)
urllib是标准库,它一个工具包模块,包含下面模块来处理url:
urllib.request 用于打开和读写url
urllib.error 包含了由urllib.request引起的异常
urllib.parse 用于解析url
urllib.robotparser 分析robots.txt 文件
Python2中提供了urllib和urllib2。urllib提供较为底层的接口,urllib2对urllib进行了进一步封装。Python3中将
urllib合并到了urllib2中,并更名为标准库urllib包。
1. urllib.request模块
模块定义了在基本和摘要式身份验证、 重定向、 cookies等应用中打开 Url (主要是 HTTP) 的函数和类。
urlopen方法
urlopen(url, data=None)
url是链接地址字符串,或请求类的实例。
data提交的数据,如果data为None发起GET请求,否则发起POST请求。见 urllib.request.Request#get_method
返回http.client.HTTPResponse类的响应对象,这是一个类文件对象。
from urllib import request
from http.client import HTTPResponse
url = "https://cn.bing.com/"
response:HTTPResponse = request.urlopen(url) # 类文件对象;
with response:
print(1,type(response)) # 类文件对象
print(2,response.headers) # 等价 info () #
print(2,response.info()) # 等价 info () #
print(3,response.geturl())
print(4,response.status,response.reason)
print(5,response.read()) # 二进制的bytes对象
#------------------------------------------------------------------------------
C:\ProgramData\Miniconda3\envs\blog\python.exe C:/Users/dell/PycharmProjects/spiders/t1.py
1 <class 'http.client.HTTPResponse'>
2 Cache-Control: private, max-age=0
Content-Length: 111838
Content-Type: text/html; charset=utf-8
Vary: Accept-Encoding
P3P: CP="NON UNI COM NAV STA LOC CURa DEVa PSAa PSDa OUR IND"
Set-Cookie: SRCHD=AF=NOFORM; domain=.bing.com; expires=Fri, 26-Aug-2022 02:43:09 GMT; path=/
Set-Cookie: SRCHUID=V=2&GUID=ECC0C843F1BA4EE7A8D5557520066A17&dmnchg=1; domain=.bing.com; expires=Fri, 26-Aug-2022 02:43:09 GMT; path=/
Set-Cookie: SRCHUSR=DOB=20200826; domain=.bing.com; expires=Fri, 26-Aug-2022 02:43:09 GMT; path=/
Set-Cookie: _SS=SID=2FDBF51342FC674231D5FA2143D266D4; domain=.bing.com; path=/
Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
X-MSEdge-Ref: Ref A: 1E707EB1AAB64DB69C0FD272FD61B7BD Ref B: BJ1EDGE0221 Ref C: 2020-08-26T02:43:09Z
Set-Cookie: _EDGE_S=F=1&SID=2FDBF51342FC674231D5FA2143D266D4; path=/; httponly; domain=bing.com
Set-Cookie: _EDGE_V=1; path=/; httponly; expires=Mon, 20-Sep-2021 02:43:09 GMT; domain=bing.com
Set-Cookie: MUID=3AC183D8072E6AED16758CEA06006BDE; samesite=none; path=/; secure; expires=Mon, 20-Sep-2021 02:43:09 GMT; domain=bing.com
Set-Cookie: MUIDB=3AC183D8072E6AED16758CEA06006BDE; path=/; httponly; expires=Mon, 20-Sep-2021 02:43:09 GMT
Date: Wed, 26 Aug 2020 02:43:08 GMT
Connection: close
上例,通过urllib.request.urlopen方法,发起一个HTTP的GET请求,WEB服务器返回了网页内容。响应的数据被
封装到类文件对象中,
通过read方法、readline方法、readlines方法获取数据,
status和reason属性表示返回的状态码,
info方法返回头信息,
User-Agent问题
上例的代码非常精简,即可以获得网站的响应数据。但目前urlopen方法通过url字符串和data发起HTTP的请求。
如果想修改HTTP头,例如useragent,就得借助其他方式。
源码中构造的useragent如下
# urllib.request.OpenerDirector
class OpenerDirector:
def __init__(self):
client_version = "Python-urllib/%s" % __version__
self.addheaders = [('User-agent', client_version)]
当前显示为 Python-urllib/3.6
有些网站是反爬虫的,所以要把爬虫伪装成浏览器。随便打开一个浏览器,复制浏览器的UA值,用来伪装。
Request类
Request(url, data=None, headers={})
初始化方法,构造一个请求对象。可添加一个header的字典。data参数决定是GET还是POST请求。
add_header(key, val) 为header中增加一个键值对。
from urllib.request import Request, urlopen
import random
# 打开一个url返回一个Request请求对象
# url = 'https://movie.douban.com/' # 注意尾部的斜杠一定要有
url = 'http://www.bing.com/'
ua_list = [
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.133 Safari/537.36",# chrome
"Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN) AppleWebKit/537.36 (KHTML, like Gecko) Version/5.0.1 Safari/537.36", # safafi
]
ua=random.choice(ua_list) #pick one
#ua需要加到请求头中
request=Request(url)
request.add_header('User-Agent', random.choice(ua_list) )
print(type(request) )
response = urlopen(request, timeout=20) # request对象或者url都可以
print(type(response))
with response:
print(1, response.status, response.getcode(), response.reason) # 状态,getcode本质上就是返回 status
print(2, response.geturl()) # 返回数据的url。如果重定向,这个url和原始url不一样
# 例如原始url是http://www.bing.com/,返回http://cn.bing.com/
print(3, response.info()) # 返回响应头headers
print(4, response.read()) # 读取返回的内容
print(5, request.get_header('User-agent'))
print(6, 'user-agent'.capitalize())
2.urllib.parse模块
该模块可以完成对url的编解码; 先看一段代码,进行编码(默认UTF-8)
from urllib import parse
# urlencode函数第一参数要求是一个字典或者二元组序列
u = parse.urlencode({'url':'http://www.magedu.com/python'})
print(u)
#---------------------------------------------------
url=http%3A%2F%2Fwww.magedu.com%2Fpython
/ %2F
= 3D
从运行结果来看冒号、斜杠、&、等号、问号等符号全部被编码了,%之后实际上是单字节十六进制表示的值。
一般来说url中的地址部分,一般不需要使用中文路径,但是参数部分,不管GET还是POST方法,提交的数据中,
可能有斜杆、等号、问号等符号,这样这些字符表示数据,不表示元字符。如果直接发给服务器端,就会导致接收
方无法判断谁是元字符,谁是数据了。
为了安全,一般会将数据部分的字符做url编码,这样就不会有歧义了。
后来可以传送中文,同样会做编码,一般先按照字符集的encoding要求转换成字节序列,每一个字节对应的十六
进制字符串前加上百分号即可。
# 网页使用utf-8编码
# https://www.baidu.com/s?wd=中
# 上面的url编码后,如下
# https://www.baidu.com/s?wd=%E4%B8%AD
from urllib import parse
u = parse.urlencode({'wd':'中'}) # 编码
url = "https://www.baidu.com/s?{}".format(u)
print(url)
print('中'.encode('utf-8')) # b'\xe4\xb8\xad'
print(parse.unquote(u)) # 解码
print(parse.unquote(url))
------------------------------------------------------
https://www.baidu.com/s?wd=%E4%B8%AD
b'\xe4\xb8\xad'
wd=中
https://www.baidu.com/s?wd=中
4.2 提交方法method
最常用的HTTP交互数据的方法是GET、POST。
GET方法,数据是通过URL传递的,也就是说数据是在HTTP报文的header部分。
POST方法,数据是放在HTTP报文的body部分提交的。
数据都是键值对形式,多个参数之间使用&符号连接。例如a=1&b=abc
from urllib import request,parse
from http.client import HTTPResponse
# ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"
base_url = "https://cn.bing.com/search"
u = parse.urlencode({'q':'马哥教育'})
url = "{}?{}".format(base_url,u)
req = request.Request(url,headers={'User-agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"})
with request.urlopen(req) as response:
with open('./bing.html','wb') as f: #注意编码;
f.write(response.read())
#-------------------------------------------------------
GET方法
连接 必应 搜索引擎官网,获取一个搜索的URL http://cn.bing.com/search?q=马哥教育
需求 : 请写程序完成对关键字的bing搜索, 将返回的结果保存到一个网页文件
from urllib import request,parse
from http.client import HTTPResponse
# ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"
# 请求什么返回什么
url = "http://httpbin.org/get"
req = request.Request(url,headers={'User-agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"})
with request.urlopen(req) as response:
print(response.read())
#------------------------------------------------------------
b'{\n "args": {}, \n "headers": {\n "Accept-Encoding": "identity", \n "Host": "httpbin.org", \n "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36", \n "X-Amzn-Trace-Id": "Root=1-5f463de0-28f2bea1625061d03c4977d3"\n }, \n "origin": "117.150.188.202", \n "url": "http://httpbin.org/get"\n}\n'
POST方法
http://httpbin.org/ 测试网站
from urllib import request,parse
from http.client import HTTPResponse
# ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"
# 请求什么返回什么
url = "http://httpbin.org/post"
req = request.Request(url,headers={'User-agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"})
params = parse.urlencode({
'id':100,
'name':'tommy'
})
with request.urlopen(req,params.encode()) as response:
print(response.read())
#- --------------------------------------------------------------
b'{\n "args": {}, \n "headers": {\n "Accept-Encoding": "identity", \n "Host": "httpbin.org", \n "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36", \n "X-Amzn-Trace-Id": "Root=1-5f463de0-28f2bea1625061d03c4977d3"\n }, \n "origin": "117.150.188.202", \n "url": "http://httpbin.org/get"\n}\n'
处理JSON数据
查看"豆瓣电影",看到"最近热门电影"的“热门” ;
热门XHR : xml http request
tag 标签“热门”,表示热门电影
type 数据类型,movie是电影
page_limit 表示返回数据的总数
page_start 表示数据偏移
通过分析,我们知道这部分内容,是通过A JAX从后台拿到的Json数据。
访问URL是https://movie.douban.com/j/search_subjects?type=movie&tag=%E7%83%AD%E9%97%A8&page_limit=50&page_start=0%E7%83%AD%E9%97%A8
是utf-8编码的中文“热门” 服务器返回的Json数据50条
from urllib import request,parse
from http.client import HTTPResponse
# ua = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"
# 请求什么返回什么
url = "https://movie.douban.com/j/search_subjects"
params = parse.urlencode({
'type':'movie',
'tag':'热门',
'page_limit':3,
'page_start':5,
})
url += '?' + params
req = request.Request(url,headers={'User-agent':"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.106 Safari/537.36"})
with request.urlopen(req) as response:
print(response.read())
-----------------------------------------------
b'{"subjects":[{"rate":"8.3","cover_x":5906,"title":"\xe5\xb0\x91\xe5\xb9\xb4\xe7\x9a\x84\xe4\xbd\xa0","url":"https:\\/\\/movie.douban.com\\/subject\\/30166972\\/","playable":true,"cover":"https://img3.doubanio.com\\/view\\/photo\\/s_ratio_poster\\/public\\/p2572166063.jpg","id":"30166972","cover_y":8268,"is_new":false},{"rate":"5.6","cover_x":1500,"title":"\xe8\xb6\x85\xe8\x83\xbd\xe8\xae\xa1\xe5\x88\x92","url":"https:\\/\\/movie.douban.com\\/subject\\/30330875\\/","playable":false,"cover":"https://img9.doubanio.com\\/view\\/photo\\/s_ratio_poster\\/public\\/p2614190404.jpg","id":"30330875","cover_y":2222,"is_new":false},{"rate":"6.2","cover_x":1080,"title":"\xe5\xa6\x99\xe5\x85\x88\xe7\x94\x9f","url":"https:\\/\\/movie.douban.com\\/subject\\/34888476\\/","playable":true,"cover":"https://img9.doubanio.com\\/view\\/photo\\/s_ratio_poster\\/public\\/p2614234255.jpg","id":"34888476","cover_y":1512,"is_new":false}]}'