文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

经过一个多星期的学习和尝试,相信大家都已经熟悉python的语法,结构,以及一些基本的包和用途了,现在我们正式准备开始采集,但是有一个大问题,就像之前采集那个美国案例站的时候,每次采集,我们都要写上好几行的指令,而且,还没继承一些类似cookie、refer之类的信息,所以,简单的使用python的urllib是不能满足我们的实际需要的,那么,我们就来分析一下,一个可以日常使用的采集类,应该包含哪些东西。

1、实例化采集类后,自带一些header信息,类似user-agent、accept之类的,能不手动添加就不手动添加

2、在执行了采集后,获取采集到的响应头,解析其中的数据,该记录的记录该执行的执行,在下次调用采集方法时继承获取到的信息

3、可以采集纯文本内容,也可以采集二进制流,方便采集页面和下载相关文档

4、支持不同的字符编码,响应编码,比如gbk、utf8等,比如gzip、deflate等

5、支持不同的请求方法,比如get、put、post、delete、head等

6、不管是否采集异常,都能返回状态码

7、可以伪造、添加各种头信息,伪造、添加cookie之类的信息,类似 Oauth:xxxx,Signture:xxx等

8、支持301、302之类的自动跳转采集,支持meta自动跳转采集

9、自动完成网址补全,不需要我们在根据采集目标,提取到链接后自己再计算

10、如果可以,尽量支持异步采集

11、如果可以,尽量支持事件委托

12、如果可以,尽量支持proxy

13、如果可以,尽量支持断点续传下载和上传

以上这些,是老顾在c#里搞了几年采集总结下来的需求,当以上这些都满足的时候,采集就是一件很简单的事情了

来,让我们一步一步的尽量实现上述需求

------------------------------------------------------------------------------------------------

我们继续把类放到之前提到的custom文件夹下,如果不了解的,请参考文盲的Python入门日记:第五天,搭建一个python调试环境,以及初步探索pymssql的使用,文中在安装完spyder后,有建立自定义类的方法

嗯,为了不与其他包混淆,我们给这个类的文件夹起名叫spider,采集类嘛,这个方便理解

嗯,开始开工,先来个基本定义,类名就用Ajax吧,反正包名和类名不一致是常态

class Ajax:
	def __init__(self):
		self.version = '0.1'
from spider import Ajax

# Reloaded modules: spider

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

嗯,类定义成功了,然后,开始一步一步的实现我们的需求吧

第一步,先定义method枚举,因为这个数量有限,具体实现先不管,我们先把method限制掉

那么,来定义枚举吧

from enum import Enum

class Ajax:
	def __init__(self):
		self.version = '0.1'
		self.agent = 'Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0'
		self.refer = ''
		self.current_url = ''
		self.method = self.Method.GET
	
	class Method(Enum):
		GET = 1
		POST = 2
		HEAD = 3
		PUT = 4
		DELETE = 5

因为这个方法就只在发起http请求的时候使用,所以,我们把Metho作为Ajax的子类型定义,顺便定义了几个变量,相信大家看到变量名就知道这些是干嘛用的了,就不解释了

然后,我们就封装请求头,请求头是个词典,但是,是多个现有项的组合,所以我们把他定义为一个属性

	@property
	def Header(self):
		return {'refer':self.refer
			,'user-agent':self.agent
			,'accept':self.accept
			,'accept-encoding':self.encoding
			,'accept-language':self.lang
			,'cache-control':self.cache}
from spider import Ajax

ajax = Ajax()
print(ajax.Header)

# {'refer': '', 'user-agent': 'Mozilla/5.0 (Windows NT 5.1; rv:11.0) Gecko/20100101 Firefox/11.0', 'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', 'accept-encoding': 'gzip, deflate, br', 'accept-language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache'}

很好,请求头信息先这样,之后后边需要修改的地方,比如添加请求头,添加cookie,我们稍后再做,先来做请求方法吧,尝试我们用自己封装的类进行第一次采集

	def Http(self,url,method=Method.GET,post=None):
		self.current_url = url
		req = request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		req.encoding = self.charset
		res = request.urlopen(req)
		data = res.read()
		self.html = data.decode(encoding=(re.sub('[-]','',self.charset)))
		return self.html

本来,印象中看到过有人使用 request.urlopen(req,headers)这样的格式,但是我在使用时居然报错,仔细百度了一下,原来还需要 urllib2 这个包支持,使用pip install urllib2,结果发现还不是官方包,那就算了,反正我们是封装到类里,只要能实现header信息携带,那就没必要开那么多包,使用自带的 add_header 就挺好

另外需要注意的是,decode里,encoding不能使用 utf-8,只能使用utf8,没有哪个减号,所以用正则处理了一下

from spider import Ajax
ajax = Ajax()
b = ajax.Http('https://www.cctv.com')
print(b)

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

轻松搞定第一次采集,那么,我们的第一个需求,自带一些header信息就算完成了,当然后边肯定还要调整,追加很多内容,但现在先这样了

同时,我们第一次采集已经成功了,但我们并没有把相应头信息拿下来,所以,现在我们去调整下代码,看看响应头信息

	def Http(self,url,method=Method.GET,post=None):
		self.current_url = url
		req = request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		req.encoding = self.charset
		res = request.urlopen(req,timeout=3)
		print(res.headers)
		data = res.read()
		self.html = data.decode(encoding=re.sub(r'[-]','',self.charset))
		return self.html
Date: Wed, 23 Jun 2021 14:35:30 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
Expires: Wed, 23 Jun 2021 14:35:39 GMT
Server: CCTVQCLOUD
Cache-Control: max-age=180
X-UA-Compatible: IE=Edge
Age: 171
X-Via: 1.1 PSbjwjBGP2sa180:8 (Cdn Cache Server V2.0), 1.1 PSgddgzk5hm168:0 (Cdn Cache Server V2.0), 1.1 chk67:1 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 60d346b2_PS-PEK-01fXq68_10786-48330

相应头回来了,但现在出大麻烦了,开始报错

self.html = data.decode(encoding=re.sub(r'[-]','',self.charset))
UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8b in position 1: invalid start byte

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

嗯。。。。这个错误还是一阵一阵的,针对错误的采集,我们直接返回data,先不解码,发现前三个字符时\x1f\x8b\x08,哦吼,这明显就不是正常的编码格式了,百度一下,果然如此 https://blog.csdn.net/weixin_36842174/article/details/88924660,看看,我们还没准备好,就遇到了第四点需求里的情况了,页面内容是gzip格式编码的,来调整下代码

嗯。。。回过头来看响应头,果然发现 gzip 消息了,来开刚才我们得到的响应头

Date: Thu, 24 Jun 2021 00:52:04 GMT
Content-Type: text/html
Transfer-Encoding: chunked
Connection: close
Expires: Thu, 24 Jun 2021 00:54:41 GMT
Cache-Control: max-age=180
X-UA-Compatible: IE=Edge
Content-Encoding: gzip
Age: 23
X-Via: 1.1 PS-000-017po25:9 (Cdn Cache Server V2.0), 1.1 PS-TSN-01hDc143:15 (Cdn Cache Server V2.0)
X-Ws-Request-Id: 60d3d734_PS-TSN-01hOH49_13803-39259

content-encoding: gzip,好吧,我们先不管编码问题,我们先处理响应头吧,然后根据响应头来处理后续内容,还是回归到第二步了~~~~

那么,开始对响应头进行解析

import re
import gzip
from enum import Enum
from urllib import request

class Ajax:
	def __init__(self):
		self.version = '0.1'
		self.agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400'
		self.refer = ''
		self.cache = 'no-cache'
		self.lang = 'zh-CN,zh;q=0.9'
		self.encoding = 'gzip, deflate, br'
		self.accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
		self.current_url = ''
		self.method = self.Method.GET
		self.charset = 'utf-8'
		self.__content_encoding = ''
		self.html = ''
	
	class Method(Enum):
		GET = 1
		POST = 2
		HEAD = 3
		PUT = 4
		DELETE = 5
		DEBUG = 6

	@property
	def Header(self):
		return {'refer':self.refer
			,'user-agent':self.agent
			,'accept':self.accept
			,'accept-encoding':self.encoding
			,'accept-language':self.lang
			,'cache-control':self.cache}

	def Http(self,url,method=Method.GET,post=None):
		self.current_url = url
		req = request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		req.encoding = self.charset
		res = request.urlopen(req,timeout=3)
		self.__headers = str(res.headers)
		self.__parseHeaders()
		data = res.read()
		enc = re.sub(r'[-]','',self.charset)
		if 'gzip' in self.__content_encoding:
			self.html = gzip.decompress(data).decode(enc)
		else:
			self.html = data.decode(encoding=enc)
		return self.html

	def __parseHeaders(self):
		dict = {n.group(1).lower():n.group(2).strip() for n in re.finditer('([^\r\n:]+):([^\r\n]+)',self.__headers)}
		if 'content-encoding' in dict:
			self.__content_encoding = dict['content-encoding']
		else:
			self.__content_encoding = ''

来一次到目前为止的所有代码,嗯,gzip已经支持起来了,deflate的碰到再说,至于这次的响应头里,没有set-cookie,所以暂时也不对cookie操作,其他方式的cookie后边再说

同时,这次的响应头里,也没什么可用的信息,那就继续进行第三点,可以采集纯文本内容,也可以采集二进制流,方便采集页面和下载相关文档

事实上,不管是访问页面也好,下载也好,都是正常的http请求,区别在于,页面的,我们可以通过浏览器打开,其他的都以二进制流直接保存到文件了,也就是说,我们的Http方法其实是可以访问可下载资源的,不过他没有相关的保存设置

所以,我们从新建立一个方法,单独用来进行下载

	def Download(self,url,filename,method=Method.GET,post=None):
		req = request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		res = request.urlopen(req,timeout=3)
		data = res.read()
		f = open(filename,'wb+')
		f.write(data)
		f.close()

来尝试一下第一个下载,还是用上次的那个 xslt 文件测试,直接下载 xlst 到 d盘看看

from spider import Ajax
ajax = Ajax()
ajax.Download('https://www.govinfo.gov/content/pkg/BILLS-117hr3237ih/xml/BILLS-117hr3237ih.xml', r'd:\\test.xml')

很好,D盘多了一个 test.xml 文件,下载功能实现,具体完善以后再说,嘿嘿嘿嘿

支持不同的字符编码,响应编码,比如gbk、utf8等,比如gzip、deflate等,嗯这个需求基本上算是已经搭好架子了,gzip已经支持了,页面编码,也可以通过实例.charset设置,嗯继续下一个了

支持不同的请求方法,比如get、put、post、delete、head等,这个也是很重要的哦,有一些第三方提供的接口,他们会要求请求方法各种各样,除了常见的get、post,put和delete也很常用,比如第三方云存储,老办法,看别人的文章 https://blog.csdn.net/qq_35959613/article/details/81068042

哦哦,又引入了一个 requests 包。。嗯嗯,我们也引入,稍微变动变动,我们就可以支持各种方法了,再参考另一个文章 https://www.cnblogs.com/jingdenghuakai/p/11805128.html

嗯嗯嗯,这些文章都是挺好的,我们先建立一个测试环境吧,要不采集现成的网站,不会返回我们需要的测试详情,做一个测试页,提交的所有内容都反馈出来,这样才好继续编写

        protected void Page_Load(object sender, EventArgs e)
        {
            Response.Write("<div><b>Request.HttpMethod</b>:" + Request.HttpMethod + "</div>");
            Response.Write("<div><b>Request.InputStream.Length</b>:" + Request.InputStream.Length.ToString() + "</div>");
            Response.Write("<div><b>Request.Files.Count</b>:" + Request.Files.Count.ToString() + "</div>");
            Response.Write("<div><b>Request.Headers</b></div>");
            foreach (string key in Request.Headers.Keys)
            {
                Response.Write("<div>" + key + " : " + Request.Headers[key] + "</div>");
            }
            Response.Write("<div><b>Request.QueryString</b></div>");
            foreach (string key in Request.QueryString.Keys)
            {
                Response.Write("<div>" + key + " : " + Request.QueryString[key] + "</div>");
            }
            Response.Write("<div><b>Request.Form</b></div>");
            foreach (string key in Request.Form.Keys)
            {
                Response.Write("<div>" + key + " : " + Request.Form[key] + "</div>");
            }
        }

老顾自己是用的IIS,用c#写了个webform直接用来测试,大家可以根据自己的情况建立测试文件,基本就需要这么写内容,就可以测试出绝大部分功能了

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

这是老顾在浏览器里测试的结果,用来做个参考

然后,刚才的文章虽然都很好,但又引入了一个包。。。。先不引入,试试看 urllib 能不能做到,仅仅追加一个 req.method 设置

	def Http(self,url,method=Method.GET,postdata=None):
		self.current_url = url
		req = request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		req.encoding = self.charset
		req.method = method.name
		res = request.urlopen(req,timeout=3)
		self.__headers = str(res.headers)
		self.__parseHeaders()
		data = res.read()
		enc = re.sub(r'[-]','',self.charset)
		if 'gzip' in self.__content_encoding:
			self.html = gzip.decompress(data).decode(enc)
		else:
			self.html = data.decode(encoding=enc)
		return self.html
import re
from spider import Ajax
ajax = Ajax()
b = ajax.Http('https://localhost/test.aspx')
b = re.sub('<div[^<>]*>','\\n',b)
b = re.sub('<[^<>]+>','',b)
print(b)
Request.HttpMethod:GET
Request.InputStream.Length:0
Request.Files.Count:0
Request.Headers
Cache-Control : no-cache
Connection : close
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding : gzip, deflate, br
Accept-Language : zh-CN,zh;q=0.9
Host : www2018.caigou.com.cn
User-Agent : Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400
Refer : 
Request.QueryString
Request.Form
import re
from spider import Ajax
ajax = Ajax()
b = ajax.Http('https://localhost/test.aspx?x=1&y=2',Ajax.Method.POST,'a=1&b=2')
b = re.sub('<div[^<>]*>','\\n',b)
b = re.sub('<[^<>]+>','',b)
print(b)
Request.HttpMethod:POST
Request.InputStream.Length:0
Request.Files.Count:0
Request.Headers
Cache-Control : no-cache
Connection : close
Content-Length : 0
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding : gzip, deflate, br
Accept-Language : zh-CN,zh;q=0.9
Host : www2018.caigou.com.cn
User-Agent : Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400
Refer : 
Request.QueryString
x : 1
y : 2
Request.Form

哦吼,httpmethod方法的确变了哦,那么就是说,使用urllib本身,就可以完成这些,不需要再引入其他包了,那就继续完善,先把post支持起来

说到post,那提交的信息也分两种,一种是表单方式,一种就是纯文字流方式。。。这也是无奈了,都支持起来吧,先支持一下表单方式

	def Http(self,url,method=Method.GET,postdata=None):
		self.current_url = url
		enc = re.sub(r'[-]','',self.charset)
		req = urllib.request.Request(url)
		for k in self.Header:
			req.add_header(k,self.Header[k])
		req.encoding = self.charset
		req.method = method.name
		if postdata!=None:
			if isinstance(postdata,dict):
				postdata = urllib.parse.urlencode(postdata)
			postdata = postdata.encode(enc)
		res = urllib.request.urlopen(req,timeout=3,data=postdata)
		self.__headers = str(res.headers)
		self.__parseHeaders()
		data = res.read()
		if 'gzip' in self.__content_encoding:
			self.html = gzip.decompress(data).decode(enc)
		else:
			self.html = data.decode(encoding=enc)
		return self.html

仅仅追加了一个 postdata 判断,并判断是否是字典,开始看到别人用urllib.parse.urlencode还不知道这个方法干嘛的,自己实际测试后发现,这就是将dict组成querystring字符串的方法。。。

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

得,我自己传递的值就是a=1&b=2了,不用转换格式了

Request.HttpMethod:POST
Request.InputStream.Length:7
Request.Files.Count:0
Request.Headers
Cache-Control : no-cache
Connection : close
Content-Length : 7
Content-Type : application/x-www-form-urlencoded
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding : gzip, deflate, br
Accept-Language : zh-CN,zh;q=0.9
Host : www2018.caigou.com.cn
User-Agent : Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400
Refer : 
Request.QueryString
x : 1
y : 2
Request.Form
a : 1
b : 2
python请求测试
div {padding:10px;font-size:14px;margin-bottom:10px;}

很好,post成功了,在 Request.Form 下有两个参数了,同样Request.InputStream也获取到信息了,那么这个是form提交方式

然后,有些api需要提交一个json字符串,那我们就来尝试一下

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

很好,可以直接用类型转换得到词典对应的json字符串,很方便

那么,就提交这个字符串试一下

Request.HttpMethod:POST
Request.InputStream.Length:16
Request.Files.Count:0
Request.Headers
Cache-Control : no-cache
Connection : close
Content-Length : 16
Content-Type : application/x-www-form-urlencoded
Accept : text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding : gzip, deflate, br
Accept-Language : zh-CN,zh;q=0.9
Host : www2018.caigou.com.cn
User-Agent : Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400
Refer : 
Request.QueryString
x : 1
y : 2
Request.Form
 : {'a': 1, 'b': 2}
python请求测试
div {padding:10px;font-size:14px;margin-bottom:10px;}

哦吼,InputStream得到了16个字符,在Request.Form里,也体现出来这个信息了,连花括号在内,正好是16个字符,所以说,post支持完成

那么再看看put和delete,直接修改method参数

ajax.Http('https://www2018.caigou.com.cn/test.aspx?x=1&y=2',Ajax.Method.PUT)

HTTPError: Method Not Allowed

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

意料之外的错误,方法不被支持?PUT和DELETE都是这个错误,HEAD到是可以用,嗯,就是页面响应内容没了,只有返回头了

现在来调试PUT和DELETE吧,在网上搜索了半天,发现有用 httplib2实现的,有用urllib2实现的,在本地sitepackages文件夹里,还发现了个urllib3包。。。。总之,就是urllib本身是无法使用delete和put方法的,毕竟这两个不算安全方法啊

看来,不得不引入urllib2包了。。。。。。。嗯???。。。。。。TNND,python3里根本没有urllib2这个包!!!python3把 urllib 和 urllib2 整合了,但不支持了 PUT 和 DELETE!!!天啊。。。一个大坑。。。算了,我换个别的包吧,requests 就不错,经过一番整改,整个代码变成了这样

import gzip
import re
import requests
import zlib
from enum import Enum

class Ajax:
	def __init__(self):
		self.version = '0.1'
		self.agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400'
		self.refer = ''
		self.cache = 'no-cache'
		self.lang = 'zh-CN,zh;q=0.9'
		self.encoding = 'gzip, deflate, br'
		self.accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
		self.current_url = ''
		self.method = self.Method.GET
		self.charset = 'utf-8'
		self.__content_encoding = ''
		self.html = ''
	
	class Method(Enum):
		GET = 1
		POST = 2
		HEAD = 3
		PUT = 4
		DELETE = 5
		OPTIONS = 6
		TRACE = 7
		PATCH = 8

	@property
	def Header(self):
		return {'refer':self.refer
			,'user-agent':self.agent
			,'accept':self.accept
			,'accept-encoding':self.encoding
			,'accept-language':self.lang
			,'cache-control':self.cache}

	def Http(self,url,method=Method.GET,postdata=None):
		self.current_url = url
		if postdata!=None:
			if isinstance(postdata,str):
				postdata = {n.group(1):n.group(2) for n in re.finditer('([^&=]+)=([^&]*)',postdata)}
		if method == self.Method.GET:
			res = requests.get(url=url,headers=self.Header,data=postdata)
		elif method == self.Method.POST:
			res = requests.post(url=url,headers=self.Header,data=postdata)
		elif method == self.Method.HEAD:
			res = requests.head(url=url,headers=self.Header,data=postdata)
		elif method == self.Method.PUT:
			res = requests.put(url=url,headers=self.Header,data=postdata)
		elif method == self.Method.DELETE:
			res = requests.delete(url=url,headers=self.Header,data=postdata)
		enc = re.sub(r'[-]','',res.encoding)
		if enc == 'ISO88591':
			enc = 'gbk'
		self.status = res.status_code
		self.__headers = str(res.headers)
		self.__parseHeaders()
		if method == self.Method.HEAD:
			return self.__headers
		data = res.content
		if 'gzip' in self.__content_encoding:
			self.html = gzip.decompress(data).decode(enc)
		elif 'deflate' in self.__content_encoding:
			try:
				self.html = zlib.decompress(data, -zlib.MAX_WBITS).decode(enc)
			except zlib.error:
				self.html = zlib.decompress(data).decode(enc)
		else:
			self.html = data.decode(encoding=enc)
		return self.html


	def __parseHeaders(self):
		dict = {n.group(1).lower():n.group(2).strip() for n in re.finditer('([^\r\n:]+):([^\r\n]+)',self.__headers)}
		if 'content-encoding' in dict:
			self.__content_encoding = dict['content-encoding']
		else:
			self.__content_encoding = ''

不想记那么多方法,一个方法能调用所有method才是我所期望的,可惜 requests.Request 不知道该怎么用,没有找到相关手册

文盲的Python入门日记:第二十三天,封装一个自定义爬虫类,用来执行日常的采集

根据帮助,他得到个 PreparedRequest 带 method 的对象,问题是,后续文档没找到,怎么让这个对象发生实际请求,并获取数据呢,经过一番搜索,还是找到了一个文章https://blog.csdn.net/weixin_44523387/article/details/90732389

原来,这个Request方法, 需要用到一个Session对象,由这个对象保持会话,并发送请求,so。。。。貌似后续的cookie保持也可以用他来实现?那么再次调整我们的方法

import gzip
import re
import requests
import zlib
from enum import Enum

class Ajax:
	def __init__(self):
		self.version = '0.1'
		self.agent = 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.25 Safari/537.36 Core/1.70.3870.400 QQBrowser/10.8.4405.400'
		self.refer = ''
		self.cache = 'no-cache'
		self.lang = 'zh-CN,zh;q=0.9'
		self.encoding = 'gzip, deflate, br'
		self.accept = 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8'
		self.current_url = ''
		self.method = self.Method.GET
		self.charset = 'utf-8'
		self.__content_encoding = ''
		self.__session = requests.Session()
		self.html = ''
	
	class Method(Enum):
		GET = 1
		POST = 2
		HEAD = 3
		PUT = 4
		DELETE = 5
		OPTIONS = 6
		TRACE = 7
		PATCH = 8

	@property
	def Header(self):
		return {'refer':self.refer
			,'user-agent':self.agent
			,'accept':self.accept
			,'accept-encoding':self.encoding
			,'accept-language':self.lang
			,'cache-control':self.cache}

	def Http(self,url,method=Method.GET,postdata=None):
		self.current_url = url
		if postdata!=None:
			if isinstance(postdata,str):
				postdata = {n.group(1):n.group(2) for n in re.finditer('([^&=]+)=([^&]*)',postdata)}
		req = requests.Request(method=method.name,url=url,headers=self.Header,data=postdata)
		pre = self.__session.prepare_request(req)
		res = self.__session.send(pre)
		enc = re.sub(r'[-]','',res.encoding)
		if enc == 'ISO88591':
			charset = re.findall('''<meta[^<>]*?charset=['"]?([^'""]+)['"\\s]?''',res.text,re.I)
			if len(charset) == 0:
				enc = re.sub(r'[-]','',self.charset)
			else:
				enc = re.sub(r'[-]','',charset[0])
		self.status = res.status_code
		self.__headers = str(res.headers)
		self.__parseHeaders()
		if method == self.Method.HEAD:
			return self.__headers
		data = res.content
		if 'gzip' in self.__content_encoding:
			self.html = gzip.decompress(data).decode(enc)
		elif 'deflate' in self.__content_encoding:
			try:
				self.html = zlib.decompress(data, -zlib.MAX_WBITS).decode(enc)
			except zlib.error:
				self.html = zlib.decompress(data).decode(enc)
		else:
			self.html = data.decode(encoding=enc)
		return self.html

	def __parseHeaders(self):
		dict = {n.group(1).lower():n.group(2).strip() for n in re.finditer('([^\r\n:]+):([^\r\n]+)',self.__headers)}
		if 'content-encoding' in dict:
			self.__content_encoding = dict['content-encoding']
		else:
			self.__content_encoding = ''

在初始化这个实例的时候,建立一个私有变量__session,用来发送这个实例中的请求,然后,就不再需要判断我的请求方法method到底是什么了,这个方法都支持了,那一大堆的if elif就扔掉吧,然后把download方法也调整一下

	def Download(self,url,filename,method=Method.GET,postdata=None):
		if postdata!=None:
			if isinstance(postdata,str):
				postdata = {n.group(1):n.group(2) for n in re.finditer('([^&=]+)=([^&]*)',postdata)}
		req = requests.Request(method=method.name,url=url,headers=self.Header,data=postdata)
		pre = self.__session.prepare_request(req)
		res = self.__session.send(pre)
		data = res.content
		f = open(filename,'wb+')
		f.write(data)
		f.close()

至此,我们列出的需求的1、2、3、4、5、6点都已经满足了,后边的需求,我们下次再继续实现

再次声明,老顾的python也是刚刚开始学习,从2021年6月6日才开始的哦,如有错漏,还请指正

上一篇:python第三方模块之chardet


下一篇:Charles 乱码问题解决