鱼C论坛

 找回密码
 立即注册
查看: 1852|回复: 1

[技术交流] 对Python爬虫框架的一点想法和一个自己的爬虫模板

[复制链接]
发表于 2021-6-6 14:23:21 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能^_^

您需要 登录 才可以下载或查看,没有账号?立即注册

x
本帖最后由 LJZheng 于 2021-6-25 23:42 编辑

不知道大家是什么时候开始学Python爬虫的?对于python的爬虫框架有多少的了解?我是2019年开始学的Python,不久后就学了爬虫。


一直以来requests库的简洁明了吸引了我,但架不住他也简洁过头了,称不上是框架,无法有效的组织起复杂的爬虫代码,稍微复杂一些的逻辑就是一坨难以维护的屎山;但是像scrapy这样的框架学起来也着实费力,主要是自己没有大的需求,学起来没动力。


因此我需要一个简洁明了的爬虫框架,简单到可以不需要import,直接复制粘贴到文件里都不会觉得难以维护。


我之前学了前端的vue,每一个组件都有一个生命周期,从创建到销毁,我只需要编写每个组件在不同的时候需要做的事情即可。同时我在学习SpringBoot的时候意识到框架的作用是让开发者更好的关心具体的业务逻辑代码。


根据我自己的想法,我写了一个适合自己的爬虫模板,代码简洁可以按需修改




  1. from collections import defaultdict
  2. import requests

  3. DEFAULT_GET_OPTIONS = {
  4.     'headers': {
  5.         'user-agent': 'Mozilla/5.0'
  6.     }
  7. }

  8. class Spider:
  9.     def __init__(self, url='', method='GET', options=DEFAULT_GET_OPTIONS, pipe: defaultdict = defaultdict(int)):
  10.         self.url = url
  11.         self.method = method
  12.         self.options = options
  13.         self.pipe = pipe
  14.         self._kill = False

  15.     def run(self):
  16.         # print(self.url)
  17.         self.before()
  18.         if self.killed():
  19.             print(f'request killed: {self.url}')
  20.         else:
  21.             res = requests.request(self.method, self.url, **self.options)
  22.             res.encoding = res.apparent_encoding
  23.             if res.status_code == 200:
  24.                 self.callback(res)
  25.             else:
  26.                 self.callback_err(res)
  27.             self.after()

  28.     def before(self):
  29.         pass

  30.     def callback(self, res: requests.Response):
  31.         pass

  32.     def callback_err(self, res):
  33.         print(res)

  34.     def after(self):
  35.         self.kill()

  36.     def killed(self):
  37.         return self._kill

  38.     def kill(self):
  39.         self._kill = True
复制代码


整个想法就是所有的爬虫都能够继承上面这个Spider的类,然后根据自己的需要修改代码,最后调用run()方法即可,具体的操作如下所示:



  1. # 定义一个自己的 Spider 类
  2. class MySpider(Spider):
  3.     def __init__(self, url='', method='GET', options=DEFAULT_GET_OPTIONS, pipe=defaultdict(int)):
  4.         # 这里也可以写点东西,但是不建议
  5.         super().__init__(url=url, method=method, options=options, pipe=pipe)

  6.     # 下面四个方法可以根据自己的需要修改
  7.     def before(self):
  8.         # 在发起请求之前做的事情
  9.         # 在这里设置 url、method、options 是有效的
  10.         return super().before()
  11.    
  12.     def callback(self, res):
  13.         # 请求发起后对res的处理
  14.         return super().callback(res)

  15.     def callback_err(self, res):
  16.         # 请求失败情况的处理
  17.         return super().callback_err(res)

  18.     def after(self):
  19.         # 请求处理后做的事情
  20.         # 比如time.sleep()、打印日志、或者调用其他的爬虫
  21.         return super().after()

  22. # 运行的方法
  23. MySpider(
  24.     # 各种参数
  25.     # url: 对 url发起请求
  26.     # method:请求的方法,默认 GET
  27.     # options: 请求的一些可选的设置,是一个字典的形式,
  28.     # pipe:传入 Spider 里的一些参数,可对 Spider 进行一些定制化的操作
  29. ).run()
复制代码


Spider中的url,method和options 对应的分别是 requests.request(method, url, **kwargs) 中的url,method和kwargs,因此options要传入一个字典,顺带一提,run()里面的代码逻辑已经非常一目了然了,就不讲了


讲一下pipe的用法,pipe其实就是一个用字典传参的东西,相当于一个容器,啥都可以往里面装,用于定制一些需要的参数(可能主要是我菜,不会用**kwargs吧。还有一个 _kill 的参数可以用来控制爬虫是否执行请求,在before()里面设置一下就行了。这里有一个图片爬取的例子:



  1. import os
  2. import time

  3. DEFAULT_IMG_GET_OPTIONS = {
  4.     'headers': {
  5.         'user-agent': 'Mozilla/5.0',
  6.         'Accept-Encoding': 'gzip, deflate'
  7.     }
  8. }

  9. def exists(path: str):
  10.     return os.path.exists(path)

  11. def mkdir(path: str):
  12.     if not os.path.exists(path):
  13.         os.makedirs(path)

  14. def write(path, data):
  15.     with open(path, 'wb') as f:
  16.         f.write(data)

  17. class Img_Spider(Spider):
  18.     """
  19.     # init run
  20.     Img_Spider(
  21.         url=r'http://images.dmzj1.com/b/%E5%88%AB%E5%BD%93%E6%AC%A7%E5%B0%BC%E9%85%B1%E4%BA%86/%E7%AC%AC1%E8%AF%9D_1491547130/01.jpg',
  22.         pipe={
  23.             'img_path': './img_path.jpg', # 图片保存的位置(文件名)
  24.             'sleep': 2 # 爬取图片后休息一下,可以防止服务器压力过大,封ip等情况。
  25.         }
  26.     ).run()
  27.     """

  28.     def __init__(self, url='', method='GET', options=DEFAULT_IMG_GET_OPTIONS, pipe=defaultdict(int)):
  29.         super().__init__(url=url, method=method, options=options, pipe=pipe)

  30.     def before(self):
  31.         path = self.pipe['img_path']
  32.         # 这里展现了 self._kill 的作用
  33.         # 如果文件存在就不发起请求
  34.         if exists(path):
  35.             print(f'文件 {path} 已存在')
  36.             self.kill()
  37.         return super().before()

  38.     def callback(self, res: requests.Response):
  39.         path = self.pipe['img_path']
  40.         write(path, res.content)
  41.         print(f'文件 {path} 已保存')
  42.         return super().callback(res)

  43.     def after(self):
  44.         # 爬取图片后休息一下,可以一定程度上防止服务器压力过大,封ip等情况。
  45.         if 'sleep' in self.pipe:
  46.             time.sleep(self.pipe['sleep'])
复制代码


我个人认为自己写的这个东西能够满足我的需要,也就够了


想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复

使用道具 举报

发表于 2021-11-29 10:10:09 | 显示全部楼层
感谢楼主分享
想知道小甲鱼最近在做啥?请访问 -> ilovefishc.com
回复 支持 反对

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Archiver|鱼C工作室 ( 粤ICP备18085999号-1 | 粤公网安备 44051102000585号)

GMT+8, 2024-5-11 03:38

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表