教程:文盲的Python入门日记:第二十八天,封装一个自定义爬虫类

优采云 发布时间: 2022-10-20 16:29

  教程:文盲的Python入门日记:第二十八天,封装一个自定义爬虫类

  1.实例化采集类后,自带一些头信息,类似于user-agent,accept等,可以手动添加,不用手动添加(已经实现)

  2、执行采集后,获取采集收到的响应头,解析里面的数据,记录执行的执行情况,下次调用采集方法时继承. 信息(已实施)

  3.可以采集纯文本内容或采集二进制流,方便采集页面和下载相关文档(已经实现)

  4.支持不同的字符编码、响应编码,如gbk、utf8等,如gzip、deflate等(已实现)

  5.支持不同的请求方式,如get、put、post、delete、head等(已实现)

  6、无论采集是否异常,都可以返回状态码(实现)

  7.可以伪造添加各种头信息,伪造添加cookies等信息,类似于Oauth:xxxx, Signature:xxx等。

  8.支持301、302采集等自动跳转,支持元自动跳转采集

  9.自动完成URL补全,我们根据采集目标提取链接后不需要自己计算

  10.如果可能,尽量支持异步采集

  11. 如果可能,尽量支持事件委托

  12.如果可能,尽量支持代理

  13.如果可能,尽量支持断点续传下载

  在上一节文章我们构建了一个自定义爬虫类,已经实现了一些需求。本文将继续执行其余要求。在我们继续修改我们的类之前,让我们谈谈 spyder。

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

  为什么先说spyder,主要是老谷找不到python包的完整使用手册。资料过于零散,整理起来很麻烦。它不像 php 手册或 msdn 命名空间介绍。的东西。就像前面几篇文章一样,每次想做点什么,都要百度很久。结果,这个系列文章是极其难产的。那么与 spyder 有什么可谈的呢?让我们来看看。. .

  打开spyder,如界面所示,分为几个不同的区域,通常情况下,我们在左侧输入py代码,右下角可以看到输出结果,右上角可以看到输出结果。. . 没有好好利用

  比如在左边,我们输入一段代码

  [n for n in range(1,100)]

  选择并执行这段代码(F9),右下控制台可以出现反馈信息

  这是一种非常方便的调试方法。当代码中有交互指令时,比如输入,右下控制台可以进行交互操作,输入一些信息。这个我就不多说了,大家都会用。

  那么,快来想办法利用右上方的区域来辅助我们的学习和工作吧

  在代码区输入代码

  import requests

req = requests()

  然后鼠标指向requests()方法,界面变了

  当我们点击悬浮提示时,右上方区域的内容发生变化,帮助页面出现大量信息

  嗯。. . 这个很方便,可以查看方法的具体用法,可惜他并没有像vs中那样列出类的所有方法,只依赖类。然后在等待spyder响应之后,看来可以使用属性或者方法了,然后去选择我们可能需要的

  现在,让我们修改命令

  import requests

req = requests.Request(url='https://www.baidu.com',method='GET')

  运行这些代码,将右上方区域切换到变量资源管理器(Variable Explorer)

  我们在左侧运行的代码生成的变量将在此处列出。只要他们没有被清除或关闭,他们就会留在这里。比如刚才的req中,我们可以看到他的描述。用鼠标双击该变量。看一看

  这是一个比较完整的实例属性和方法。它列出了该实例的所有可用方法和属性,并且可以展开查看。这样就可以避免之前找不到方法的问题,直接给一个变量赋值给这个类,然后去这里看看能不能代替vs中的对象浏览器。

  好吧,简单说一下spyder。毕竟,老谷完全是靠自己摸索学会了蟒蛇,而且年纪大了,看其他视频都觉得不舒服,所以慢慢来。至于其他python相关的IDE有没有提供这样的查询方式,老顾不知道,刚转行的同学可以自行探索。然后,我们回到主题并继续研究我们的爬行动物。

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

  7.可以伪造添加各种头信息,伪造添加cookies等信息,类似于Oauth:xxxx, Signature:xxx等。

  一般来说,同一个站点上需要频繁更改header信息和cookie信息的地方很少。再说说反爬网站,等我们进入采集实战的时候再去处理。让我们先谈谈它。常规 网站。所以,实例化一次,设置好这个信息,基本上这个实例全站都可以用采集,后面再讲线程问题。

  所以,这一次,我们把目光投向了天眼查站,并尝试根据他的响应来调整我们的代码,让它可以伪造cookies和接收headers

  先到先无调整采集

  from spider import Ajax

ajax = Ajax()

html = ajax.Http('https://www.tianyancha.com',Ajax.Method.GET)

print(ajax.status,html)

  很好,可以正常的采集天眼查首页,但是我们知道天眼查会返回一些cookie信息,我们现在还没有收到,所以我们搜索一下cookie存放在哪里

  因为对各种包不熟悉,先在爬虫类中添加两个属性

   @property

def ResposeHeader(self):

return self.__headers

@property

def Session(self):

return self.__session

  一个用于返回响应头信息,一个用于返回会话信息,我们看看

  好小子。. . 响应头真的有这么多数据吗?548?哦,展开的时候,没有那么多,548字节,吓死我了。

  粘贴响应头信息并整理

  {

'Date': 'Wed, 30 Jun 2021 02:25:43 GMT',

'Content-Type': 'text/html; charset=utf-8',

'Transfer-Encoding': 'chunked',

'Connection': 'keep-alive',

'Set-Cookie': 'aliyungf_tc=8e44b1cb0fc5f37d29864918aa197ec6ed802b989655bf8732efdcd291861558; Path=/; HttpOnly, acw_tc=76b20f8c16250199433016427e4b75bd21ba7840934a083d22e010ebf1aedd;path=/;HttpOnly;Max-Age=1800, csrfToken=s4i4TN-WIKaLgWAXW3qHwbK5; path=/; secure, TYCID=784de430d94a11eb8216f7b2b73bb5b3; path=/; expires=Fri, 30 Jun 2023 02:25:43 GMT; domain=.tianyancha.com',

'Content-Encoding': 'gzip'

}

  哦嗬,我找到了第一个关键信息,Set-Cookie,这是服务器发给浏览器的cookie信息。Set-Cookie就是其中一种方式,写下来,以后再处理

  然后,查看会话中的内容

  显然cookie的信息也保存在session中,里面有4个cookie。

  print(ajax.Session.cookies)

  

  我有一个我不知道的对象列表,让我们不要管它。总之,这里有cookies,所以现在我们需要自己定义一个cookies变量来存储这些信息,并在下次继承采集,好吧,我们必须支持从外部添加cookies。另外,老谷注意到了这个cookie中的域问题。伪造cookies和域信息也很重要。请参考老谷的另一个文章,有的网站在校验cookies的时候会比较严格。,不再是特定domian的cookie是无法识别的,python的cookie处理是意料之中的。. . .

  在 __init__ 中,附加一个属性

  self.cookies = requests.utils.cookiejar_from_dict({})

  然后,我们可以通过实例添加cookie

  import re

from spider import Ajax

ajax = Ajax()

# 为了获取初始cookie,先访问下天眼查首页

ajax.Http('https://www.tianyancha.com')

ajax.cookies.set('tyc-user-info', '{***********}', domain='.tianyancha.com')

ajax.cookies.set('auth_token', '****************', domain='.tianyancha.com')

html = ajax.Http('https://www.tianyancha.com/company/2968548568',Ajax.Method.GET)

# 显示现在已有cookies

print(ajax.cookies)

print(re.findall(r'[\s\S]*?(?=)',html,re.I))

  很好,cookie伪造成功,域名带前缀点的问题不成问题。证据是他没有提示登录,其次电话号码没有被星号隐藏。

  在这段代码中,我们使用了两次 Http 方法。第一次是获取初始cookie。如果没有初始cookie,那么我们需要自己通过cookies.set方法添加初始cookie。老顾懒得加了,让他自动获取,然后在第二个采集之前,我们追加两个cookie,继承第一个采集的cookie,这样就正确得到了我们预期的结果,并且cookies在同一个实例中,只需要添加一次。当我们的ajax实例再次使用Http访问天眼查看其他企业信息时,无需关注cookie信息。

  剩下的就是伪造请求头了。之前,我们的 Header 定义是一个固定的字典。现在我们需要对其进行修改以使其成为动态字典。同样,向 __init__ 添加两个赋值

   self.__requestHeaders = {}

self.__refreshRequestHeaders()

  调整Header属性的实现

   @property

def Header(self):

return self.__requestHeaders

  然后,添加一个私有方法来初始化请求头信息

   def __refreshRequestHeaders(self):

self.__requestHeaders.update({'refer':self.refer

,'user-agent':self.agent

,'accept':self.accept

,'accept-encoding':self.encoding

,'accept-language':self.lang

,'cache-control':self.cache})

  最后,我们在 Ajax 类中添加一个公共方法 AddHeader,用于向请求头添加信息

   def AddHeader(self,key:str = None,val:str = None,dic:dict = None):

if dic != None and isinstance(dic,dict):

self.__requestHeaders.update(dic)

if key != None and val != None:

self.__requestHeaders.update({key:val})

  from spider import Ajax

ajax = Ajax()

ajax.AddHeader(dic={'oauth':'userinfo'})

ajax.AddHeader('pwd','***')

print(ajax.Header)

  运行它并查看结果

  很好,请求头信息已经更新了,虽然有时候需要删除一些请求头,这里我就不实现了。有需求可以自己实现,那么第七个需求就告一段落了,下一部分开始Handle jump issues。

  8.支持301、302采集等自动跳转,支持元自动跳转采集

  让我们找到一个带有重定向的 URL,例如: ,一个用短链接*敏*感*词*生成的地址

  来,我们试试,这个请求会发生什么

  from spider import Ajax

ajax = Ajax()

html = ajax.Http('http://m6z.cn/6uVNKg')

print(ajax.status,ajax.ResposeHeader)

print(html)

  他自动跳了过去!返回的状态码也是200!中间的301和302的过程就省略了!, 原来你不想自动 301, 302,你要设置这个参数allow_redirects=False,算了,让他自动跳转,不过我们还是加了个开关,可以用来关闭这个自动跳转,在 __init__ 中附加一个属性

  self.redirect = True

  修改发送请求的send参数

  res = self.__session.send(request=pre,allow_redirects=self.redirect)

  然后就可以成功禁用自动301、302

  那么,自动跳转也需要支持meta跳转,后面会讲到,因为无论是meta跳转还是js跳转,都涉及到一个url补全问题,我们先解决这个,再来支持meta跳转和js跳转

  9.自动完成URL补全,我们根据采集目标提取链接后不需要自己计算

  在日常的采集过程中,我们经常会遇到页面中的链接地址缺少域名,有的有域名但没有协议的情况。. . 还有其他各种不应该存在的协议。. . . HMMMMMMM,反正经历了很多,自然知道了

  这次我们来试试政财网首页

  from spider import Ajax

ajax = Ajax()

#html = ajax.Http('http://news.baidu.com/ns?word=school&ie=gb2312&cl=2&rn=20&ct=0&tn=newsrss&class=0')

html = ajax.Http('http://www.ccgp.gov.cn/')

print(html)

  可以看出页面中没有URL的链接有N多条,所以我们需要在采集的时候进行处理,得到完整的链接地址,方便我们后面处理。这个时候,就是常客大显身手的时候了。顺便说一下,在做这个补全之前,先看看scrapy有没有补全,好像很多人用scrapy。

  好,我们上最简单的scrapy 采集,不管多少,就采集每个首页

  先在命令行运行几条命令

  d:\>pip install scrapy

<p>

d:\>scrapy startproject ccgp

d:\>cd ccgp

d:\ccgp>scrapy genspider ccgp_gather www.ccgp.gov.cn

</p>

  为ccgp创建一个采集确实很简单

  然后修改其中一些文件

  找到settings.py,修改robotstxt_obey,不验证robots.txt

  找到middlewares.py,修改process_request方法,这里追加user-agent信息

  找到ccgp_gather.py,修改parse方法,将我们采集的首页内容保存到

  然后回到命令行运行采集

  d:\ccgp>scrapy crawl ccgp_gather

  很好,这个页面被采集撤下了,我们来看看

  好吧,他也没有完成 url。对了,感觉用scrapy做采集有点麻烦。我之前建过n多个xml,针对自己的采集规则有完整的内容,什么翻页采集,什么时间范围采集,什么标题过滤,我们做采集,很少全站采集,也很少有脑残的采集,所以这个scrapy如果要达到上面的要求,感觉还是挺麻烦的,每个站都做一次。. . 我还不如把所有站点信息放到一个xml中,使用统一规则,使用自己的爬虫解析器一次采集多个站点。总而言之,scrapy和老顾是形影不离的。但是,如果你使用scrapy,它不会影响阅读这篇文章。不要放弃,继续阅读。

  回到我们自己的 url 补全,然后在 Ajax 类中添加一个私有方法 __url_complemented。在http方法之前,返回html,用这个方法改正后返回

  嗯。. . . . 分析,url在哪里?同学们,列出来

  .

  .

  .

  .

  .

  .

  有href,很常见,a标签,链接标签

  还有src,也很常见,script标签,img标签,embed等。

  还有一些容易被忽略的 URL,它们存在于样式、样式文件和元数据中。. . .

  而且更难找,定位,开放,存在于js中。. . . 动作,存在于表单标签中

  好吧,越来越复杂了。. . 我这里只实现前两个,添加一个元,其他的我不考虑。

  补全url其实很简单。使用正则表达式提取url,验证url是否为合法url。当然,特殊情况要除外,what about:blank,什么file:///,什么base64数据(图片src可能有这种情况)。. . 总之,只计算需要补全的url,人家自己已经有了协议,不再操作。

  哪些需要填写?

  1、没有协议,比如//,鬼知道是http还是https。. . 其实这是由当前页面协议决定的。你在http域名页面点击这个链接,结果是你在https域名页面点击这个链接,结果是

  2、如果链接地址路径不完整,比如/superwefei,需要填写域名才能获取/superwefei。这种情况比较复杂,可能在需要计算路径的时候遇到,比如../../../image/xxx.shtml,也可能遇到非标准路径../image/xxx.shtml

  这两种情况基本都是老顾遇到的。如果还有其他情况,可以通知老顾,继续学习。

  下面是实现代码

<p> def __url_complemented(self,html):

html = re.sub(&#39;&#39;&#39;(url|src|href)\\s*=\\s*([&#39;"]?)([^&#39;"]*)(\\2|[\\s\\r\\n\\t])&#39;&#39;&#39;,self.__url_replace,html,count=0,flags=re.I)

return html

def __url_replace(self,m):

url = m.group(3).strip()

# about:setting、about:blank 类型的,javascript:void(0) 类型的,#类型的,原样返回

if re.search(&#39;^(#.*|javascript:.*|[a-z_-]+:[a-z_-]+)$&#39;,url,re.I):

return m.string[m.span()[0]:m.span()[1]]

# 带有协议的,原样返回,例如 https://、ftp://、file://、tencent://等

if re.search(&#39;^[a-z]+://&#39;,url,re.I):

return m.string[m.span()[0]:m.span()[1]]

# base64 格式,原样返回

if re.search(&#39;^base64&#39;,url,re.I):

return m.string[m.span()[0]:m.span()[1]]

root = re.sub(r&#39;^([a-z]+:/{2,}[^/]+).*&#39;,&#39;\\1/&#39;,self.current_url.strip(),re.I)

if re.search(&#39;^/(?!/)&#39;,url,re.I):

url = re.sub(&#39;^/&#39;,root,url,re.I)

elif re.search(&#39;^//&#39;,url):

url = re.sub(&#39;^([a-z]+:)//.*$&#39;,&#39;\\1&#39;,root,re.I) + url

else:

path = re.sub(&#39;/[^/]*$&#39;,&#39;&#39;,self.current_url) + &#39;/&#39;

p = re.search(r&#39;^[\./]+&#39;,url,re.I)

if p:

# 具有 ./ 计算路径

# 获取开头的全部计算路径

p = p.string[p.span()[0]:p.span()[1]]

# 去掉路径中 ./ 后,剩余的点的数量,就是路径向上路径的层级

p = re.sub(r&#39;\./&#39;,&#39;&#39;,p)

# 获得剩余点的数量,得到层级

p = len(p)

pth = path

for i in range(p):

pth = re.sub(&#39;[^/]+/&#39;,&#39;&#39;,pth,re.I)

if len(pth)

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线