Scrapy

scrapy基本使用

scrapy工作流程

  • 常规爬虫逻辑:主url->url list->request->response->data

  • scrapy爬虫逻辑

    • engine引擎:做整体调度

    • 先找到spider中的起始url:main_url,拿到引擎中包装成request对象

    • 将request对象传递给scheduler调度器(本质:队列)

    • 队列再通过引擎传给downloader(下载器):发送网络请求,得到响应,下载器对其封装为response对象(带着页面源代码)返回给引擎 (提取页面源代码的过程)

    • 引擎再将response送回spider (spider送出url,收回response)

    • spider中的parse()对数据进行解析(xpath,re,json,css......)

    • spider将数据传回引擎,引擎再传给pipline(管道)进行数据存储

  • 实际需要自己写的就是spider和pipline

 

第一个项目

创建

终端里:scrapy startproject game

  • game为整个项目的根目录

  • __init__.py

  • items.py:封装比较大的数据需要用

  • middlewares.py:中间件处理反爬、cookie

  • piplines.py:数据存储 可以有多个管道

  • settings.py:对项目整体配置信息

  • spiders包下的__init__.py 放爬虫

生成一个新的爬虫

生成一个爬虫在终端game目录下scrapy genspider xiao 4399.com

  • xiao是爬虫的名称 4399.com是对域名的限制,4399.com以外的内容不爬取

  • 该操作在spider下创建一个xiao.py

运行项目

运行scrapy项目:scrapy crawl xiao

  • 在setting.py中设置日志级别:

    • 日志的级别:DEBUG,INFO,WARNING,ERROR CRITICAL

    • LOG_LEVEL = "WARNING" #WARING级以上的可以打印

#xiao.py
import scrapy


class XiaoSpider(scrapy.Spider):
   name = "xiao"  #爬虫名字
   allowed_domains = ["4399.com"] #允许爬取域名
   start_urls = ["http://www.4399.com/flash/"] #起始页面url

   def parse(self, response): #该方法默认是用来处理解析
       #拿到页面源代码
       # print(response.text)
       # response.json() #如果返回为json
       # response.css() #用css选择器
       #获取页面中所有的游戏名字
       # txt = response.xpath("//ul[@class='n-game cf']/li/a/b/text()").extract()
       # print(txt)

       #分块提取
       li_list = response.xpath("//ul[@class='n-game cf']/li")
       for li in li_list:
           #extract_first 安全 即使没提取到内容也不会报错 返回None
           name = li.xpath("./a/b/text()").extract_first()
           category = li.xpath("./em/a/text()").extract_first()
           date = li.xpath("./em/text()").extract_first()
           dict = {
               "name":name,
               "category":category,
               "datae":date
          }
           #需要用yield将数据传递给管道
           yield  dict  #如果返回的是数据,直接可以认为是给了管道pipline



 

管道处理

通过yield dict将数据传给pipline

 

管道:

  • 默认关闭

  • key 就是管道的路径

  • value 是管道的优先级,数越小,优先级越高

  • 可以有多个管道

ITEM_PIPELINES = {
 
   "game.pipelines.GamePipeline": 300,
}

新建一个管道:

class GamePipeline:  #类名可以修改
   #处理数据的专用方法,item数据,spider是爬虫
   def process_item(self, item, spider):  #方法固定
       print(item)
       print(spider.name)
       return item

class NewPipeline:
   def process_item(self, item, spider):  #方法固定
       item['thrift2'] = "管道2"
       return item
   
#setting.py中
ITEM_PIPELINES = {
   #key 就是管道的路径
   #value 是管道的优先级,数越小,优先级越高
   "game.pipelines.GamePipeline": 300,
#先执行NewPipeline
   "game.pipelines.NewPipeline": 200
}

中间件

  • 引擎到下载器之间,可以加入一个下载器中间件,处理代理,cookie

  • 引擎和爬虫之间,可以加入一个爬虫中间件

整个流程套路

  1. 创建项目 scrapy startproject 项目名称

  2. 进入项目 cd 项目名称

  3. 创建爬虫 scrapy genspider 爬虫名字 域名

  4. 可能需要修改start_urls,修改成要抓取的页面

  5. 对数据进行解析,在spider里面的parse(response)方法中进行解析

    1. response.text

    2. response.xpath()

      1. xpath()返回的是Selector对象,需要使用extract()提取数据

      2. extract()返回列表,extract_first()返回一个数据

      3. yield 返回数据 ->把数据交给pipeline来进行持久化存储

    3. response.css()

  6. 在pipeline中完成数据的存储

    1. class 类名(): #类名可修改

    2. def process_item(self,item,spider): #每一个管道的方法固定

    3. item:数据,spider:爬虫

    4. return item #必须要return东西,否则下一个管道收不到数据

  7. 设置settings.py文件把pipeline进行生效文件

    ITEM_PIPELINES = {    '管道的路径':优先级, #优先级越小,优先级越高    "game.pipelines.GamePipeline": 300,    #先执行NewPipeline    "game.pipelines.NewPipeline": 200}

     

  8. 运行爬虫

scrapy crawl xiao

数据清洗

  • 入库前清晰

    • 对字符串进行处理

      • \t\n strip()

      • '123456' int()

      • '23456.4356' float()

      • str = '10万人' if '万' in str:

  • 入库后清晰

    • 数据挖掘、数据分析

 

请求对象

scrapy.Request(url[,callback,method,headers,body,cookies,meta,dont_filter=False])
  1. 中括号中参数为可选参数

  2. callback:表示当前的url响应交给哪个函数去处理

  3. meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,如下载延迟,请求深度等

  4. dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为True,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会反复被请求,否则程序不会启动

  5. method

  6. headers:接受一个字典,其中不包括cookies

  7. cookies:接受一个字典,专门放置cookies

  8. body:接收json字符串,为post的数据,发送payload_post请求时使用

管道

  • yield希望返回的是一个item

  • 在items.py中预设key值

class CaipiaoItem(scrapy.Item):
   # define the fields for your item here like:
   name = scrapy.Field()   #相当于字典里的key
   qihao = scrapy.Field()
   red_ball = scrapy.Field()
   blue_ball = scrapy.Field()
   pass
  • 爬虫中

    • 处理空列表见方法1,2

    • 存数据使用items提供的方法

import scrapy
from caipiao.items import CaipiaoItem

class ShuangseqiuSpider(scrapy.Spider):
   name = "shuangseqiu"
   allowed_domains = ["500.com"]
   start_urls = ["http://datachart.500.com/ssq/"]

   def parse(self, response):
       trs = response.xpath("//tbody[@id='tdata']/tr")
       for tr in trs:
           #方法2
           if tr.xpath("./@class").extract_first() == 'tdbck':
               continue
           # red_ball = tr.xpath('./td[@class="chartBall01"]/text()').extract()
           # print(red_ball)
           #scrapy支持xpath和css混用
           red_ball = tr.css(".chartBall01::text").extract()
           #提取到的数据中有空列表
           #方法1 #方法2见上方
           # if not red_ball:
           #     continue
           blue_ball = tr.css(".chartBall02::text").extract_first()
           qihao = tr.xpath('./td[1]/text()').extract_first()
           # dic = {
           #     "red_ball" :red_ball,
           #     "blue_ball":blue_ball,
           #     "qihao":qihao
           # } 放弃这种写法

           cai = CaipiaoItem() # cai = dict()
           cai['qihao']=qihao
           cai['red_ball']=red_ball
           cai['blue_ball']=blue_ball
           yield cai
  • 存储数据的方案:

    1. 数据分析:csv

    2. 业务逻辑:mysql

    3. 大批量数据储备不清楚如何使用:mongodb

    4. 文件的存储

存入csv

"f-string"(格式化字符串字面值)是Python 3.6版本中引入的一种字符串格式化方式,它允许在字符串中嵌入表达式,以大括号 {} 表示。在这里,f 是指该字符串是一个"f-string",其中大括号 {} 内的表达式会被执行,并将结果插入到字符串中。

在这个代码行中,使用 f-string 格式化字符串,以使变量 item 中的数据插入到字符串中。具体来说,使用 { } 包含的表达式来引用 item 字典中的值,以在字符串中创建包含数据的文本行。在表达式中使用引号括起来的逗号和换行符来定义输出文本的格式。

class CaipiaoPipeline:
   """在爬虫开始就打开这个文件
  在执行过程中,不断往里存储数据
  在执行完毕时,关掉这个文件
  """

   def open_spider(self,spider):
       self.f = open("./双色球.csv",mode="a",encoding="utf-8")
   def close_spider(self,spider):
       if self.f:
           self.f.close()
   def process_item(self, item, spider):
       #在process中 是每条数据执行一次 所以mode="a"
       self.f.write(f"{item['qihao']},{'_'.join(item['red_ball'])},{item['blue_ball']}\n")
       return item

 

存入mysql

  • 在open_spider中与数据库建立连接

    def open_spider(self,spider):
       self.conn = pymysql.connect(
           host="", #ip地址
           port=3306,  #端口号
           user="",  #用户名
           password="******",
           database="python_data" #数据库名
      )
  • 在使用pymysql.connect() 方法与数据库建立连接后,想要操作数据库时,就需要使用游标 Cursor通过连接好的数据库(此处为cursor)调用 cursor() 方法即可返回一个新的游标对象,在连接没有关闭之前,游标对象可以反复使用

cursor = self.conn.cursor()
  • 写sql语句,并用cursor执行

sql = "insert into caipiao(qihao,red_ball,blue_ball) values (%s, %s, %s)"
           #必须给一个元组
           cursor.execute(sql,(item['qihao'],"_".join(item["red_ball"]),item["blue_ball"]))
           self.conn.commit()
  • Connection类提供了三个方法: begin 开始事务, commit 提交事务, rollback 回滚事务,如果通过 sql 语句对数据库中的数据进行了修改, 则需要提交事务。

self.conn.commit()
  • rollback()也是个很重要的方法,正确的使用rollback可以避免commit提交事务的时候发生错误导致程序中断。主要使用方式:结合try except捕获异常,将事务进行rollback回滚

self.conn.rollback()
  • 关闭连接

if cursor:
   cursor.close()
  • 完整代码

class CaipiaoMySQLPipeline:
   def open_spider(self,spider):
       self.conn = pymysql.connect(
           host="1.14.65.81",
           port=3306,
           user="python_data",
           password="802365",
           database="python_data"
      )

   def close_spider(self,spider):
       if self.conn:
           self.conn.close()

   def process_item(self, item, spider):
       try:
           cursor = self.conn.cursor()
           sql = "insert into caipiao(qihao,red_ball,blue_ball) values (%s, %s, %s)"
           #必须给一个元组 三个%s对应下面元组中的内容
           cursor.execute(sql,(item['qihao'],"_".join(item["red_ball"]),item["blue_ball"]))
           self.conn.commit()
       except:
           self.conn.rollback()
       finally:
           if cursor:
               cursor.close()
       return item

 

  • 可以将mysql信息直接设置到setting.py

#settings.py
MYSQL = {
   "host": "1.14.65.81",
   "port": 3306,
   "user": "python_data",
   "password": "802365",
   "database": "python_data"
}

 

#pipeline对应修改地方
from caipiao.settings import MYSQL

def open_spider(self,spider):
   self.conn = pymysql.connect(
       host=MYSQL['host'],
       port=MYSQL['port'],
       user=MYSQL['user'],
       password=MYSQL['password'],
       database=MYSQL['database']
  )

 

存入mongodb

  • 创建一个client:self.client = pymongo.MongoClient(host='1.14.65.81', port=27017)

  • 连接具体的数据库:db = self.client['python'] 此处选择了python数据库

  • 链接数据库中的集合:self.collection = db['caipiao']

  • 往集合中插入:self.collection.insert_one({"qihao": item['qihao'],"red_ball":item['red_ball'],"blue_ball":item['blue_ball']})

  • 关闭client:self.client.close()

  • 完整代码

class CaipiaoMongodbPipeline:
   """在爬虫开始就打开这个文件
  在执行过程中,不断往里存储数据
  在执行完毕时,关掉这个文件
  """

   def open_spider(self,spider):
       #创建一个client
       self.client = pymongo.MongoClient(host='1.14.65.81', port=27017)
       #连接具体的数据库python
       db = self.client['python'] #use database 操作哪一个数据库
       #链接数据库python中的集合caipiao

       self.collection = db['caipiao']  #指定彩票集合


   def close_spider(self,spider):
       if self.client:
           self.client.close()


   def process_item(self, item, spider):
       self.collection.insert_one({"qihao": item['qihao'],"red_ball":item['red_ball'],"blue_ball":item['blue_ball']})
       return item

 

存入文件(涉及到了全站数据爬取)

深度爬取https://www.umei.cc/bizhitupian/weimeibizhi/网站图片

  1. spider

    • 生成爬虫scrapy genspider tupian umei.cc

    • parse()中

      • scrapy.Request()可以将内容封装成Request

      • response.urljoin(href)将拿到的href和最初的响应拼接

      • callback()回馈函数,可以理解为将这个请求交给引擎,当一系列操作结束后,返回的response直接交给parse_detail

      • 当整个循环结束之后,表面该页中所有详情页已经爬取完,拿取下一页的url并封装为Request

          def parse(self, response):
             #通过页面源代码拿到列表
             divs = response.xpath('//*[@id="infinite_scroll"]/div')
             for div in divs:
                 href = div.xpath('./div/div/a/@href').extract_first()
                 yield scrapy.Request(
                     url = response.urljoin(href),
                     method='get',
                     callback=self.parse_detial
                )
                 
             #此时可以考虑爬取下一页
             #如果可以下一页,当么数据的解析,直接就是当前这个parse来完成
             #a标签的text()中是否包含“下一页”
             next_href = response.xpath('//*[@id="pageNum"]/a[contains(text(),"下一页")]/@href').extract_first()
             if next_href:
                 #进行下一页的请求
                 yield scrapy.Request(
                     url=response.urljoin(next_href),
                     method="get",
                     callback = self.parse
                )
    • parse_detail()中

      • 常规操作,将子页面中获取的信息添加到Item,并yield item

  2. pipelines

    • class WeimeiSavePipeline(ImagesPipeline) 图片下载

      • 继承于ImagesPipeline,from scrapy.pipelines.imgaes import ImagesPipeline;类中必须重载三个方法

      • def get_media_requests(self,item,info) 获取下载请求

        • 直接返回一个图片下载请求:return scrapy.Requests(item['img_src'])

      • def file_path(self,request,response=None,info=None, *, item=None) 准备存储路径

        • request.url可以直接获取request的url

        • file_name = request.url.split("/")[-1]

        • 返回 f"img/{file_name}" #img/xxx.jpg #保存路径

      • def item_completed(self, results, item, info)

        • 该步可以通过print(results)看明白

        • ok,info = results[0]

        • item['local_path'] = finfo["path"] #存储图片在本地的位置

        • return item

    • class WeimeiPipeline

      • 常规三个函数

      •     def open_spider(self, spider):
               self.conn = pymysql.connect(
                   host=MYSQL['host'],
                   port=MYSQL['port'],
                   user=MYSQL['user'],
                   password=MYSQL['password'],
                   database=MYSQL['database']
              )
      •     def close_spider(self, spider):
               if self.conn:
                   self.conn.close()
      •     def process_item(self, item, spider):
               try:
                   cursor = self.conn.cursor()
                   sql = "insert into weimei(title,img_src,local_path) values (%s, %s, %s)"
                   # 必须给一个元组 三个%s对应下面元组中的内容
                   cursor.execute(sql, (item['title'],  item["img_src"], item['local_path']))
                   self.conn.commit()
               except:
                   self.conn.rollback()
               finally:
                   if cursor:
                       cursor.close()
               return item
  3. settings.py

    1. #我修改的
      ITEM_PIPELINES = {
        "weimei.pipelines.WeimeiPipeline": 300,
        "weimei.pipelines.WeimeiSavaPipeline": 290,

      }

      #日志
      LOG_LEVEL = "WARNING"

      #图片下载必须设置的一个路径
      IMAGES_STORE = "./weimei"


      #处理报错
      MEDIA_ALLOW_REDIRECTS = True

      #MYSQL配置
      MYSQL = {
         "host": "1.14.65.81",
         "port": 3306,
         "user": "python_data",
         "password": "802365",
         "database": "python_data"
      }

 

 

scrapy模拟登录

scrapy携带cookies直接获取需要登录后的页面

应用场景

 

  1. cookie过期时间长

  2. 能在过期之前拿到数据

 

实例:模拟登录github

  • 重写start_requests(self)

    • 把cookie封装为字典

class LoginSpider(scrapy.Spider):
   name = "login"
   allowed_domains = ["github.com"]
   start_urls = ["https://github.com/Mj52893366"]

   def start_requests(self):
       url = self.start_urls[0]
       temp="""
      _octo=GH1.1.256122359.1677799452; _device_id=f523c44375764c2b332dff509eee4fb3; user_session=2WYlpi77EupPgVGGrJcFIdFQQNIJo1IOoWh62lAAf1tDdmXi; __Host-user_session_same_site=2WYlpi77EupPgVGGrJcFIdFQQNIJo1IOoWh62lAAf1tDdmXi; logged_in=yes; dotcom_user=Mj52893366; has_recent_activity=1; color_mode={"color_mode":"auto","light_theme":{"name":"light","color_mode":"light"},"dark_theme":{"name":"dark","color_mode":"dark"}}; preferred_color_mode=light; tz=Asia/Shanghai; _gh_sess=zhPwqhdDKtZXjJ7fUq9g+IOhsL1ybrBRb4lcltLKdRzSSI1usQheKeIocAQjSMU6M/Yr9OIcf7QW9gz2zDKwGatc9itTjcQP3NchnWRqezEZeEo6AOeHejStkwg7JD92d63FyLR69xXD19NaFHdYrY4cjOGZDDd2G37bBNQYgleEGHSqA6Pj2SAVVTyRJ/hO2gVe8R0AzZI2Sk5vySFg8AC+1djoZbPcoPqrrEhstmICivrkquhClTCp79XTMa5UTpmxMP+LIzwiCc26kEPTYNPQyYih--aOa2uVnxhDwfj/Ma--3XGmRMJ+yNW5qfx5iGtJig==
      """
       cookies = {data.split('=')[0]: data.split('=')[-1] for data in temp.split('; ')}
       yield scrapy.Request(
           url=url,
           callback=self.parse,
           cookies=cookies
      )


   def parse(self, response):
       print(response.xpath('/html/head/title/text()').extract_first())

 

FromRequest发送post请求

思路分析

  1. 找到post的url地址:点击登录按钮进行抓包,然后定位url地址为:https://github.com/login

  2. 找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中

  3. 是否登录成功:通过请求个人主页,观察是否包含用户名

实例

import scrapy


class Git2Spider(scrapy.Spider):
   name = "git2"
   allowed_domains = ["github.com"]
   start_urls = ["https://github.com/login"]

   def parse(self, response):
       #从登录页面响应中解析出post数据   session

       token = response.xpath('//input[@name="authenticity_token"]/@value').extract_first()
       post_data = {
           'commit': 'Sign in',
           'authenticity_token': token,
           'login': "528916069@qq.com",
           'password': '8023mingji',
           'webauthn-support': 'supported',
           'webauthn-iuvpaa-support': 'supported',
           'return_to': 'https://github.com/login',
           'timestamp': '1679664015261',
           'timestamp_secret': '825163a6374b1cf95bd9b8314f4bfca7e1b50f1610d4b56136d9da0bd514d926'
      }

       #针对登录url发送post登录请求
       yield scrapy.FormRequest(
           url='https://github.com/session',
           callback=self.after_login,
           formdata=post_data  #表单参数
      )
   #登录成功后得到的响应交给after_login
   def after_login(self,response):
       #针对个人管理发请求
       yield scrapy.Request(
           url='https://github.com/Mj52893366',
           callback=self.check_login
      )
   #after_login登录成功后对个人主页请求的返回的响应交给check_login
   def check_login(self,response):
       print(response.xpath('/html/head/title/text()').extract_first())

 

 

中间件

下载中间件

需要在settings中启用

  • 位置:引擎和下载器之间

  • 作用:批量拦截到整个工程中所有的请求和响应

  • 拦截请求:

    • UA伪装:

          def process_request(self, request, spider):
             #UA伪装
             request.headers['User-Agent'] = random.choice(self.user_agent_list)
             #验证代理的操作是否生效
             return None

       

    • 代理ip::

          def process_exception(self, request, exception, spider):
             # 代理
             # 使用http还是https根据请求的url
             if request.url.split(':')[0] == 'http':
                 request.meta['proxy'] = 'http://' + random.choice(self.PROXY_http)
             else:
                 request.meta['proxy'] = 'https://' + random.choice(self.PROXY_https)

             return request #将修正之后的请求对象进行重新的请求发送

       

  • 拦截响应:

    • 篡改响应数据,响应对象

    • 需求:爬取网易新闻中的新闻数据

      1. 通过网易新闻的首页解析出五大板块对应的详情页的url

      2. 每一个板块对应的新闻标题都是动态加载出来的

      3. 通过解析出每一条新闻详情页的url获取详情页的页面源码,解析出新闻内容

  • 详细内容见wangyi项目

 

wangyi

  • middlewares.py

    • process_response()

      • 拦截返回的响应,进行篡改

      • 使用构造

        #通过该方法拦截五大板块对应的响应对象,进行篡改
       def process_response(self, request, response, spider):  #spider表示的时爬虫对象
           #挑选出指定的响应对象进行篡改
           #通过url指定request
           #通过request指定response
           bro = spider.bro #获取了爬虫类中定义的浏览器对象
           if request.url in spider.models_urls:
               bro.get(request.url) #对五个板块对应的url进行请求发送
               sleep(2)
               page_text = bro.page_source #包含了动态加载的新闻数据
               #response #五大板块对应的响应对象
               #针对定位到的这些response进行篡改
               #实例化一个新的响应对象(符合需求:包含动态加载的新闻数据) 用其替代原来旧的响应对象
                   #基于selenium便捷的获取动态加载数据
               new_response = HtmlResponse(url=request.url,body=page_text,encoding='utf-8',request=request)
               return new_response
           else:
               #response #其他请求对应的响应对象
               return response
  • wangyi.py

    import scrapy
    from selenium import webdriver
    from wangyipro.items import WangyiproItem

    class WangyiSpider(scrapy.Spider):
       name = "wangyi"
       allowed_domains = ["163.com"]
       start_urls = ["https://news.163.com/"]
       models_urls = []  # 存储五个板块对应的详情页的url

       # 实例化一个浏览器对象
       def __init__(self):
           #调用selenium的方法
           self.bro = webdriver.Chrome(executable_path='E:/Pycharm/scrapy/chromedriver.exe')


       # 解析五大板块对应的详情页的url
       def parse(self, response):
           li_list = response.xpath('//div[@class="ns_area list"]/ul/li')
           alist = [1,2,3,4,5]
           for index in alist:
               model_url = li_list[index].xpath('./a/@href').extract_first()
               self.models_urls.append(model_url)

           # 依次对每一个板块对应的页面进行请求
           for url in self.models_urls:
               yield scrapy.Request(
                   url=url,
                   callback=self.parse_model
              )

       # 解析每一个板块页面中对应新闻的标题和新闻详情页的url
       # 每一个板块对应的新闻标题相关的内容都是动态加载
       def parse_model(self,response):
           div_list = response.xpath('//div[@class="ndi_main"]/div')
           for div in div_list:
               title = div.xpath('./div/div[1]/h3/a/text()').extract_first()

               new_detail_url = div.xpath('./div/div[1]/h3/a/@href').extract_first()
               item = WangyiproItem()
               item['title'] = title
               # 对新闻详情页的url发起请求
               yield scrapy.Request(
                   url=new_detail_url,
                   callback=self.parse_detail,
                   meta={'item': item}
                )

       # 用于解析新闻详情页
       def parse_detail(self,response):
           content = response.xpath('//div[@class="post_body"]//text()').extract()
           content = ''.join(content)
           item = response.meta['item']
           item['content'] = content

           yield item


       def closed(self,spider):
           self.bro.quit()

     

 

异步爬虫

异步爬虫的方式

  • 多线程,多进程(不建议)

    • 优点:为相关阻塞的操作单独开启线程或进程,阻塞操作就可以异步执行

    • 缺点:无法无限制的开启多线程或多进程

  • 线程池,进程池(适当的使用)

    • 优点:降低系统对进程或线程创建和销毁的一个频率,从而很好的降低系统的开销

    • 缺点:池中线程或进程的数量是有上限

     

 

线程池的基本使用

import time
#导入线程池模块对应的类
from multiprocessing.dummy import Pool
#使用线程池方式执行
start_time = time.time()
def get_page(str):
   print("正在下载 :",str)
   time.sleep(2)
   print('下载成功:',str)

name_list =['xiaozi','aa','bb','cc']

#实例化一个线程池对象
pool = Pool(4)
#将列表中每一个列表元素传递给get_page进行处理。
pool.map(get_page,name_list)
pool.close()
pool.join()
end_time = time.time()
print(end_time-start_time)

梨视频案例:

import requests
from lxml import etree
import re
from multiprocessing.dummy import Pool
#需求:爬取梨视频的视频数据
headers = {
   'User-Agent':'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'
}
#原则:线程池处理的是阻塞且较为耗时的操作

#对下述url发起请求解析出视频详情页的url和视频的名称
url = 'https://www.pearvideo.com/category_5'
page_text = requests.get(url=url,headers=headers).text

tree = etree.HTML(page_text)
li_list = tree.xpath('//ul[@id="listvideoListUl"]/li')
urls = [] #存储所有视频的链接and名字
for li in li_list:
   detail_url = 'https://www.pearvideo.com/'+li.xpath('./div/a/@href')[0]
   name = li.xpath('./div/a/div[2]/text()')[0]+'.mp4'
   #对详情页的url发起请求
   detail_page_text = requests.get(url=detail_url,headers=headers).text
   #从详情页中解析出视频的地址(url)
   ex = 'srcUrl="(.*?)",vdoUrl'
   video_url = re.findall(ex,detail_page_text)[0]
   dic = {
       'name':name,
       'url':video_url
  }
   urls.append(dic)
#对视频链接发起请求获取视频的二进制数据,然后将视频数据进行返回
def get_video_data(dic):
   url = dic['url']
   print(dic['name'],'正在下载......')
   data = requests.get(url=url,headers=headers).content
   #持久化存储操作
   with open(dic['name'],'wb') as fp:
       fp.write(data)
       print(dic['name'],'下载成功!')
#使用线程池对视频数据进行请求(较为耗时的阻塞操作)
pool = Pool(4)
pool.map(get_video_data,urls)

pool.close()
pool.join()


协程

单线程+异步协程(推荐)

import asyncio
async def request(url):
   print('正在请求的url是:',url)
   print('请求成功',url)
   return url
  • async 定义一个协程

    import asyncio
    async def request(url):
       print('正在请求的url是:',url)
       print('请求成功',url)
       return url

     

  • await 用来挂起阻塞方法的执行。

  • coroutine:协程对象,我们可以将协程对象注册到事件循环中,它会被事件循环调用。 我们可以使用 async 关键字来定义一个方法,这个方法在调用时不会立即被执行,而是返回 一个协程对象

    coroutine = request('www.baidu.com')
  • event_loop:事件循环,相当于一个无限循环,我们可以把一些函数注册到这个事件循环上, 当满足某些条件的时候,函数就会被循环执行。

    loop = asyncio.get_event_loop()
  • task:任务,它是对协程对象的进一步封装,包含了任务的各个状态。

    # task的使用
    loop = asyncio.get_event_loop()
    #基于loop创建了一个task对象
    task = loop.create_task(coroutine)
    print(task)
    loop.run_until_complete(task)
    print(task)
  • future:代表将来执行或还没有执行的任务,实际上和 task 没有本质区别。

    # future的使用
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(coroutine)
    print(task)
    loop.run_until_complete(task)
    print(task)
  • 绑定回调

    def callback_func(task):
       #result返回的就是任务对象中封装的协程对象对应函数的返回值
       print(task.result())

    #绑定回调
    loop = asyncio.get_event_loop()
    task = asyncio.ensure_future(coroutine)
    #将回调函数绑定到任务对象中
    task.add_done_callback(callback_func) #默认将task传入
    loop.run_until_complete(task)

 

 

posted @ 2023-04-23 10:43  qfzwy  阅读(179)  评论(0)    收藏  举报