zoukankan      html  css  js  c++  java
  • 【Python数据分析】Python3多线程并发网络爬虫-以豆瓣图书Top250为例

        基于上两篇文章的工作

       【Python数据分析】Python3操作Excel-以豆瓣图书Top250为例

       【Python数据分析】Python3操作Excel(二) 一些问题的解决与优化

        已经正确地实现豆瓣图书Top250的抓取工作,并存入excel中,但是很不幸,由于采用的串行爬取方式,每次爬完250页都需要花费7到8分钟,显然让人受不了,所以必须在效率上有所提升才行。

        仔细想想就可以发现,其实爬10页(每页25本),这10页爬的先后关系是无所谓的,因为写入的时候没有依赖关系,各写各的,所以用串行方式爬取是吃亏的。显然可以用并发来加快速度,而且由于没有同步互斥关系,所以连锁都不用上。

        既然考虑并发,那么就有多进程和多线程两种方式,各自的优缺点比较可以见:这里 

        简单来说,多进程稳定,因为一个进程挂掉其他进程不受影响,但是开销大,建立太多进程会消耗系统大量资源,并且切换慢,因为要通过系统进程调度。

        多线程作为“轻量级的进程”,是操作系统调度的基本单位,切换快速,只消耗极少的资源,但是缺点就是一个线程崩掉整个进程包括其他线程都会崩掉,所以稳定性欠佳。

        这里虽然进程数/线程数很少(只有10个),即使采用多进程也不会有多大的开销,但是为了更快地爬取,且爬取豆瓣这样的大站,稳定性不会太差,所以还是采用多线程比较实惠。

        多线程有两个模块,一个Thread模块,一个threading模块,但是前者现在用的很少了,后者更加方便实用。所以采用后者。

        在程序中实用线程有两种方法,一种是自己写一个class,并重写此class中的__init__方法和run()方法,创建一个这个class的对象并调用start()时run()方法自动调用。另一种是在threading.Thread构造函数中传入要用线程运行的函数及其参数。我采用的是后者。

        多线程主代码如下:

        thread = []
        
        for i in range(0,250,25):
            geturl = url + "/start=" + str(i)                     #要获取的页面地址
            print("Now to get " + geturl)
            t = threading.Thread(target=crawler,
                                 args=(s,i,url,header,image_dir,worksheet,txtfile))
            thread.append(t)
    
        for i in range(0,10):
            thread[i].start()
    
        for i in range(0,10):
            thread[i].join()

        以前的爬取和存储函数写到了crawler中,具有7个参数。将10页的url都放入thread列表中,然后逐个启动,启动后调用join()等待每一个线程结束,如果不等待的话,会发现有的已经运行到下面关闭文件了,那么别的没运行完的就写不了了。

        更改和简化后的全部代码如下:

    # -*- coding:utf-8 -*-
    import requests
    import re
    import xlsxwriter
    from bs4 import BeautifulSoup
    from datetime import datetime
    import codecs
    import threading
    
    #下载图片
    def download_img(imageurl,image_dir,imageName = "xxx.jpg"):
        rsp = requests.get(imageurl, stream=True)
        image = rsp.content
        path = image_dir + imageName +'.jpg'
        with open(path,'wb') as file:
            file.write(image)
    
    def crawler(s,i,url,header,image_dir,worksheet,txtfile):
        postData = {"start":i}                                #post数据
        res = s.post(url,data = postData,headers = header)    #post
        soup = BeautifulSoup(res.content.decode(),"html.parser")       #BeautifulSoup解析
        table = soup.findAll('table',{"width":"100%"})        #找到所有图书信息的table
        sz = len(table)                                       #sz = 25,每页列出25篇文章
        for j in range(1,sz+1):                               #j = 1~25
            sp = BeautifulSoup(str(table[j-1]),"html.parser") #解析每本图书的信息
    
            imageurl = sp.img['src']                          #找图片链接
            bookurl = sp.a['href']                            #找图书链接
            bookName = sp.div.a['title']
            nickname = sp.div.span                            #找别名
            if(nickname):                                     #如果有别名则存储别名否则存’无‘
                nickname = nickname.string.strip()
            else:
                nickname = ""
            
            notion = str(sp.find('p',{"class":"pl"}).string)   #抓取出版信息,注意里面的.string还不是真的str类型
            rating = str(sp.find('span',{"class":"rating_nums"}).string)    #抓取平分数据
            nums = sp.find('span',{"class":"pl"}).string                    #抓取评分人数
            nums = nums.replace('(','').replace(')','').replace('
    ','').strip()
            nums = re.findall('(d+)人评价',nums)[0]
            download_img(imageurl,bookName)                     #下载图片
            book = requests.get(bookurl)                        #打开该图书的网页
            sp3 = BeautifulSoup(book.content,"html.parser")     #解析
            taglist = sp3.find_all('a',{"class":"  tag"})       #找标签信息
            tag = ""
            lis = []
            for tagurl in taglist:
                sp4 = BeautifulSoup(str(tagurl),"html.parser")  #解析每个标签
                lis.append(str(sp4.a.string))
            
            tag = ','.join(lis)        #加逗号
            the_img = "I:\douban\image\"+bookName+".jpg"
            writelist=[i+j,bookName,nickname,rating,nums,the_img,bookurl,notion,tag]
            for k in range(0,9):
                if k == 5:
                    worksheet.insert_image(i+j,k,the_img)
                else:
                    worksheet.write(i+j,k,writelist[k])
                txtfile.write(str(writelist[k]))
                txtfile.write('	')
            txtfile.write(u'
    ')
    
    def main():
        now = datetime.now()             #开始计时
        print(now)
    
        txtfile = codecs.open("top2501.txt",'w','utf-8')
        url = "http://book.douban.com/top250?"
    
        header = { "User-Agent": "Mozilla/5.0 (Windows NT 6.3; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.13 Safari/537.36",
               "Referer": "http://book.douban.com/"
               }
    
        image_dir = "I:\douban\image\"
        #建立Excel
        workbookx = xlsxwriter.Workbook('I:\douban\booktop250.xlsx')
        worksheet = workbookx.add_worksheet()
        format = workbookx.add_format()
        #format.set_align('justify')
        format.set_align('center')
        #format.set_align('vjustify')
        format.set_align('vcenter')
        format.set_text_wrap()
    
        worksheet.set_row(0,12,format)
        for i in range(1,251):
            worksheet.set_row(i,70)
        worksheet.set_column('A:A',3,format)
        worksheet.set_column('B:C',17,format)
        worksheet.set_column('D:D',4,format)
        worksheet.set_column('E:E',7,format)
        worksheet.set_column('F:F',10,format)
        worksheet.set_column('G:G',19,format)
        worksheet.set_column('H:I',40,format)
    
        item = ['书名','别称','评分','评价人数','封面','图书链接','出版信息','标签']
        for i in range(1,9):
            worksheet.write(0,i,item[i-1])
    
        s = requests.Session()      #建立会话
        s.get(url,headers=header)
    
        thread = []
        
        for i in range(0,250,25):
            geturl = url + "/start=" + str(i)                     #要获取的页面地址
            print("Now to get " + geturl)
            t = threading.Thread(target=crawler,
                                 args=(s,i,url,header,image_dir,worksheet,txtfile))
            thread.append(t)
    
        for i in range(0,10):
            thread[i].start()
    
        for i in range(0,10):
            thread[i].join()
        
        end = datetime.now()    #结束计时
        print(end)
        print("程序耗时: " + str(end-now))
        txtfile.close()
        workbookx.close()
    
    if __name__ == '__main__':
        main()
    

         虽然还是写的有点乱。。 然后运行:

    2016-03-29 08:48:37.006681
    Now to get http://book.douban.com/top250?/start=0
    Now to get http://book.douban.com/top250?/start=25
    Now to get http://book.douban.com/top250?/start=50
    Now to get http://book.douban.com/top250?/start=75
    Now to get http://book.douban.com/top250?/start=100
    Now to get http://book.douban.com/top250?/start=125
    Now to get http://book.douban.com/top250?/start=150
    Now to get http://book.douban.com/top250?/start=175
    Now to get http://book.douban.com/top250?/start=200
    Now to get http://book.douban.com/top250?/start=225
    2016-03-29 08:49:44.003378
    程序耗时: 0:01:06.996697

        只花费了1分6秒,与前面的7分24秒相比,加速比达到6.7!这就是多线程的优势,理论上应该达到将近10倍的,但是由于线程创建和切换也是有开销的,所以达到7~8倍就不错了。然后我又运行了几次,稳定性还行,没有崩过。 ps:这个博客模板默认换行怎么辣么多,代码里面都自动换行。。

        (完)

  • 相关阅读:
    android之AlertDialog 点击其它区域自己主动消失
    leetCode191/201/202/136 -Number of 1 Bits/Bitwise AND of Numbers Range/Happy Number/Single Number
    CEF 框架使用集锦
    Qt WebEngine Debugging and Profiling
    Qt内置浏览器引擎WebEngine调试和分析方法
    QWebEngine踩坑记录
    带外(out of band)数据
    碰撞检测算法:点和矩形碰撞、点和圆形碰撞、矩形碰撞、圆形碰撞
    windows 7 安装visual studio 2019 闪退问题解决
    最小二乘法
  • 原文地址:https://www.cnblogs.com/whatbeg/p/5331266.html
Copyright © 2011-2022 走看看