gevent协程,提高IO效率

使用了协程的简单改进

使用gevent非常简单,只需要将任务放进将要开启的协程列表中即可,会根据IO操作切换任务,速度明显变快,需要注意的是monkey.patch_all()需要放在request前面进行编译,否则会报错。通过观察图片可以发现,并没有按照歌单的顺序处理,而是随机的。也就是对于异步IO来说,IO操作谁先处理完,谁先返回。

#coding=utf-8
import gevent
from gevent import monkey
#标记IO非阻塞,针对标准库
monkey.patch_all()#这玩意得在request上面
import threading
import time
import csv
import requests
from urllib import request
from io import BytesIO
from PIL import Image
from queue import Queue
from threading import currentThread
from bs4 import BeautifulSoup


q = Queue()  # 生成一个队列用来存取id,这种方式存取id不需要使用锁
lock = threading.Lock()
sig=1

# 消费者通过生产者类传过来的用户id提取歌单的详细信息
def Consumer():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
    }
    #lock.acquire()
    with open("./music.csv","a",encoding="utf-8-sig") as file:
        write=csv.writer(file)
        ids=q.get()
        if (ids is None) or (len(ids)==0):
            print("queue is empty")
            print("%s end"%(currentThread().name))
            sig=0
            return
        for id in ids:
            url = 'https://music.163.com/' + id['href']  # 生产者传递的id链接
            print("get url: %s"%(url))
            response = requests.get(url=url, headers=headers)
            html = response.text
            soup = BeautifulSoup(html, 'html.parser')

            img = soup.select('img')[0]['data-src']  # 图片链接
            title = soup.select('title')[0].get_text()  # 标题
            idd = soup.select('.s-fc7')[0]['href'].split('=')[-1]#创建者id
            nickname = soup.select('.s-fc7')[0].get_text()  # 昵称
            description = soup.select('p')[1].get_text()  # 简介
            count = soup.select('strong')[0].get_text()  # 播放次数
            song_number = soup.select('span span')[0].get_text()  # 歌的数目
            add_lis = soup.select('a i')[1].get_text()  # 添加进列表次数
            share = soup.select('a i')[2].get_text()  # 分享次数
            comment = soup.select('a i')[4].get_text()  # 评论次数
            write.writerow([title,idd,nickname,description,count,song_number,add_lis,share,comment])
            #写文件时,为了防止争抢时写串行,加锁,写完这部分即释放

            #接下来是处理图片
            res=requests.get(url=img,headers=headers)
            image = Image.open(BytesIO(res.content))
            try:
                image.save("./pic/"+str(time.time())+'.jpg')
            #处理报错 OSError: cannot write mode RGBA as JPEG
            except:
                image.save("./pic/"+str(time.time()) + '.png')

    #lock.release()
#生产者类产生用户id,将用户id传给消费者
def Producer(url):
        headers = {
            'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.132 Safari/537.36'
        }
        response = requests.get(url=url, headers=headers)
        html = response.text
        soup = BeautifulSoup(html, 'html.parser')
        ids = soup.select('.dec a')  # 获取包含歌单详情页网址的标签
        q.put(ids)#将id放入队列中

def main():

    url=[]
    #保存url网址
    for i in range(0,1505,35):
        url_item=f'https://music.163.com/discover/playlist/?order=hot&cat=%E6%AC%A7%E7%BE%8E&limit=35&offset={i}'
        #网易云非常阴险的在com/和discover之间加了一个奇怪的字符#/,导致我之前爬虫的时候死活爬不出内容
        url.append(url_item)
    #采用线程池的方式
    #采用协程的方式 gevent
    url_list=[gevent.spawn(Producer,url_i) for url_i in url]#生成一个任务列表
    gevent.joinall(url_list)#使用协程进行IO操作,并且按顺序返回结果
    print("producer done")
    print("consumer begin")
    print(q.qsize())
    #下面先固定csv的格式
    with open("./music.csv","a",encoding="utf-8-sig") as csvfile:#之前用utf-8不行,用utf-8-sig行
        writer = csv.writer(csvfile)
        writer.writerow(['歌单标题', '创建者id', '创建者昵称','介绍','播放量','歌曲数量','添加到播放列表的次数','分享次数','评论数'])
    #一开始设置了10个协程,导致只爬了350个歌单,刚好等于10*35,说明并没有执行循环后的操作
    url_list_C=[gevent.spawn(Consumer) for i in range(q.qsize())]#生成多少个页面个协程的列表,来抓取,类似于n个线程,否则需要做循环爬取的任务
    gevent.joinall(url_list_C)#使用协程进行IO操作,并且按顺序返回结果

    print("mission finished")


if __name__ == "__main__":
    main()

功能改进

1、以筛选播放量超过500为例

好像也用不上yield

count = soup.select('strong')[0].get_text()  # 播放次数
if int(count)<500:
    continue

2、根据csv文件信息,筛选url

这个功能可以用到yield,仿照示例,可以做个consumer和producer进行url传输和访问

可以添加如下功能,判断完一个IO后,传给consumer函数让该函数慢慢进行IO的写入内核端操作

# -*- coding: utf-8 -*-

import csv


#读是一个IO操作
def producer(conn):#conn为传入
    with open("D:\经管大三\现代程序设计\week14\music.csv","r",encoding="utf-8") as f:
        print("开始筛选")
        conn.send(None)
        reader = csv.reader(f)#相当于生成一个迭代,每次读是一个IO操作
        lis=next(reader)#先把第一行的名称部分删除,注意每行记录之间有空行
        while True:#我们希望先读取一个,然后进行解析判断,跳过读取部分
            try:
                lis=next(reader)#一个读的IO操作
                lis=next(reader)#真正读取了一个数据行,当读完最后一个空行,再执行时,引发报错
                if int(lis[4])>100000:#播放量大于100000,则将id传给consumer
                    conn.send(int(lis[1]))#将值传给迭代器运行
            except StopIteration:
                print("ending")
                conn.send("nothing")

#写是一个IO操作
def consumer():
    with open("./筛选的url.txt","a") as f:
        while True:
            print(f'开始写入')
            id=yield 1
            if isinstance(id,str):#说明已经筛选完了
                break
            url='https://music.163.com/' + str(id)
            f.write(url+'\n')


def main_1():
    conn=consumer()
    producer(conn)


if __name__ == '__main__':
    main_1()
    # main_2()
    pass

str(id)
f.write(url+‘\n’)

def main_1():
conn=consumer()
producer(conn)

if name == ‘main’:
main_1()
# main_2()
pass



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值