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
-
引擎和爬虫之间,可以加入一个爬虫中间件
整个流程套路
-
创建项目
scrapy startproject 项目名称 -
进入项目
cd 项目名称 -
创建爬虫
scrapy genspider 爬虫名字 域名 -
可能需要修改start_urls,修改成要抓取的页面
-
对数据进行解析,在spider里面的parse(response)方法中进行解析
-
response.text
-
response.xpath()
-
xpath()返回的是Selector对象,需要使用extract()提取数据
-
extract()返回列表,extract_first()返回一个数据
-
yield 返回数据 ->把数据交给pipeline来进行持久化存储
-
-
response.css()
-
-
在pipeline中完成数据的存储
-
class 类名(): #类名可修改
-
def process_item(self,item,spider): #每一个管道的方法固定
-
item:数据,spider:爬虫
-
return item #必须要return东西,否则下一个管道收不到数据
-
-
设置settings.py文件把pipeline进行生效文件
ITEM_PIPELINES = { '管道的路径':优先级, #优先级越小,优先级越高 "game.pipelines.GamePipeline": 300, #先执行NewPipeline "game.pipelines.NewPipeline": 200} -
运行爬虫
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])
-
中括号中参数为可选参数
-
callback:表示当前的url响应交给哪个函数去处理
-
meta:实现数据在不同的解析函数中传递,meta默认带有部分数据,如下载延迟,请求深度等
-
dont_filter:默认为False,会过滤请求的url地址,即请求过的url地址不会继续被请求,对需要重复请求的url地址可以把它设置为True,比如贴吧的翻页请求,页面的数据总是在变化;start_urls中的地址会反复被请求,否则程序不会启动
-
method
-
headers:接受一个字典,其中不包括cookies
-
cookies:接受一个字典,专门放置cookies
-
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
-
存储数据的方案:
-
数据分析:csv
-
业务逻辑:mysql
-
大批量数据储备不清楚如何使用:mongodb
-
文件的存储
-
存入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/网站图片
-
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
-
-
-
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
-
-
-
settings.py
-
#我修改的
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直接获取需要登录后的页面
应用场景:
-
cookie过期时间长
-
能在过期之前拿到数据
实例:模拟登录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请求
思路分析:
-
找到post的url地址:点击登录按钮进行抓包,然后定位url地址为:
https://github.com/login -
找到请求体的规律:分析post请求的请求体,其中包含的参数均在前一次的响应中
-
是否登录成功:通过请求个人主页,观察是否包含用户名
实例:
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 #将修正之后的请求对象进行重新的请求发送
-
-
拦截响应:
-
篡改响应数据,响应对象
-
需求:爬取网易新闻中的新闻数据
-
通过网易新闻的首页解析出五大板块对应的详情页的url
-
每一个板块对应的新闻标题都是动态加载出来的
-
通过解析出每一条新闻详情页的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)

浙公网安备 33010602011771号