zoukankan      html  css  js  c++  java
  • 多线程or多进程爬虫案例

    前置说明

    关于python多线程和多进程的说明,请参考如下:

    https://zhuanlan.zhihu.com/p/46368084 (一位知乎用户)

    https://www.liaoxuefeng.com/wiki/1016959663602400/1017628290184064 (廖雪峰)

    这是我找到的两篇很棒的文章,里面详细说明的python多进程、多线程的原理以及用法,大家可以仔细看看

    多进程爬虫例子

    用一个实例说明下如何使用多进程进行爬虫

    目标网站:https://imgbin.com/,本次爬取的也是一个图片网站,里面是一些透明背景图

     1.首先看一下不添加多进程/多线程时的爬取速度,代码如下

    # -*- coding:utf-8 -*-
    import requests
    from requests.exceptions import RequestException
    import os, time
    from lxml import etree
    
    
    def get_html(url):
        """获取页面内容"""
        response = requests.get(url, timeout=15)
        # print(response.status_code)
        try:
            if response.status_code == 200:
    
                # print(response.text)
                return response.text
            else:
                 return None
        except RequestException:
            print("请求失败")
            # return None
    
    def parse_html(html_text):
        """解析页面内容,提取图片url"""
        html = etree.HTML(html_text)
    
        if len(html) > 0:
            img_src = html.xpath("//img[@class='photothumb lazy']/@data-original")  # 元素提取方法
            # print(img_src)
            return img_src
    
        else:
            print("解析页面元素失败")
    
    def get_image_content(url):
        """请求图片url,返回二进制内容"""
        try:
            r = requests.get(url, timeout=15)
            if r.status_code == 200:
                return r.content
            return None
        except RequestException:
            return None
    
    
    def main(depth=None):
        """
        主函数,下载图片
        :param depth: 爬取页码
        :return:
        """
        j = 1
        base_url = 'https://imgbin.com/free-png/naruto/'  # 定义初始url
    
        for i in range(1, depth):
            url = base_url + str(i)  # 根据页码遍历请求url
            html = get_html(url)  # 解析每个页面的内容
            # print(html)
            if html:
                list_data = parse_html(html)  # 提取页面中的图片url
                root_dir = os.path.dirname(os.path.abspath('.'))
                save_path = root_dir + '/pics/'   # 定义保存路径
                for t in list_data:  # 遍历每个图片url
                    try:
                        file_path = '{0}{1}.{2}'.format(save_path, str(j), 'jpg')
                        if not os.path.exists(file_path):  # 判断是否存在文件,不存在则爬取
                            with open(file_path, 'wb') as f:
                                f.write(get_image_content(t))
                                f.close()
                                print('第{}个文件保存成功'.format(j))
                        else:
                            print("第{}个文件已存在".format(j))
                        j = j + 1
                    except FileNotFoundError as  e:
                        print("遇到错误:", e)
                        continue
    
                    except TypeError as f:
                        print("遇到错误:", f)
                        continue
            else:
                print("无结果")
    
    
    if __name__ == '__main__':
        start = time.time()
        main(3)
        end = time.time()
        print(end-start)

    测试了一下,晚上10点多,在当时的网速下,爬取2页图片,大概用了403s,并且下载失败了几张;

     2.使用多进程爬取

    如果要进行多进程爬取的话,必须要有一个准备并行执行的函数,既然要多进程爬取图片,所以应该把下载图片的功能定义为主函数

    而上面代码中的main()函数不适合作为主函数,它是用爬取页码作为参数的,而我们并行执行时并不是一次爬取多页,而是并行爬取多个图片(有点绕)

    需要改造一下:

    (1)定义一个函数,来提取所有页面的图片url,并存到一个列表中(下面代码中的第39行:get_all_image_url()函数)

    (2)定义一个主函数,接收图片url,然后下载图片(下面代码中的第82行:main()函数)

    代码如下

      1 # -*- coding:utf-8 -*-
      2 import requests
      3 from requests.exceptions import RequestException
      4 from bs4 import BeautifulSoup
      5 import bs4
      6 import os, time
      7 from hashlib import md5
      8 from lxml import etree
      9 from multiprocessing import Pool, Lock, cpu_count
     10 
     13 def get_html(url):
     14     response = requests.get(url, timeout=15)
     15     # print(response.status_code)
     16     try:
     17         if response.status_code == 200:
     18 
     19             # print(response.text)
     20             return response.text
     21         else:
     22              return None
     23     except RequestException:
     24         print("请求失败")
     25         # return None
     26 
     27 
     28 def parse_html(html_text):
     29     html = etree.HTML(html_text)
     30 
     31     if len(html) > 0:
     32         img_src = html.xpath("//img[@class='photothumb lazy']/@data-original")  # 元素提取方法
     33         # print(img_src)
     34         return img_src
     35 
     36     else:
     37         print("解析页面元素失败")
     38 
     39 def get_all_image_url(page_number):
     40     """
     41     获取所有图片的下载url
     42     :param page_number: 爬取页码
     43     :return: 所有图片url的集合
     44     """
     45 
     46     base_url = 'https://imgbin.com/free-png/naruto/'
     47     image_urls = []
     48 
     49     x = 1  # 定义一个标识,用于给每个图片url编号,从1递增
     50     for i in range(1, page_number):
     51         url = base_url + str(i)  # 根据页码遍历请求url
     52         try:
     53             html = get_html(url)  # 解析每个页面的内容
     54             if html:
     55                 data = parse_html(html)  # 提取页面中的图片url
     56                 # print(data)
     57                 # time.sleep(3)
     58                 if data:
     59                     for j in data:
     60                         image_urls.append({
     61                             'name': x,
     62                             'value': j
     63                         })
     64                         x += 1  # 每提取一个图片url,标识x增加1
     65         except RequestException as f:
     66             print("遇到错误:", f)
     67             continue
     68     # print(image_urls)
     69     return image_urls
     70 
     71 def get_image_content(url):
     72     """请求图片url,返回二进制内容"""
     73     # print("正在下载", url)
     74     try:
     75         r = requests.get(url, timeout=15)
     76         if r.status_code == 200:
     77             return r.content
     78         return None
     79     except RequestException:
     80         return None
     81 
     82 def main(url, name):
     83     """
     84     主函数:实现下载图片功能
     85     :param url: 图片url
     86     :param name: 图片名称
     87     :return:
     88     """
     89     save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'
     90     try:
     91         file_path = '{0}/{1}.jpg'.format(save_path, name)
     92         if not os.path.exists(file_path):  # 判断是否存在文件,不存在则爬取
     93             with open(file_path, 'wb') as f:
     94                 f.write(get_image_content(url))
     95                 f.close()
     96 
     97                 print('第{}个文件保存成功'.format(name))
     98         else:
     99             print("第{}个文件已存在".format(name))
    100 
    101     except FileNotFoundError as f:
    102         print("第{}个文件下载时遇到错误,url为:{}:".format(name, url))
    103         print("报错:", f)
    104         raise
    105 
    106     except TypeError as e:
    107         print("第{}个文件下载时遇到错误,url为:{}:".format(name, url))
    108         print("报错:", e)
    109 
    110 
    111 if __name__ == '__main__':
    112     start = time.time()
    113     urls = get_all_image_url(3)  # 获取所有图片url列表,爬取2页内容
    114     # print(urls)
    115     # print(cpu_count())  # 查看电脑是几核的
    116 
    117     pool = Pool(6)  # 我的电脑是6核的,所以开启6个线程试试
    118 
    119     for t in urls:  # 遍历列表中的每个图片下载url
    120         # print(i)
    121         pool.apply_async(main, args=(t["value"], t["name"]))  # 使用apply_async函数实现多进程(并行请求url,下载图片)
    122 
    123     pool.close()
    124     pool.join()
    125 
    126     end = time.time()
    127     print(end-start)

    开启了6个进程,晚上10点多,同样爬取2页内容,大概用了30s,速度提升还是挺明显的

     多线程爬虫例子

    看了开头分享的两篇文章后,应该了解到如下2点:

    1、python解释器有GIL全局锁,导致多线程不能利用多核,多线程并发并不能在python中实现;

    2、任务类型分为计算密集型IO密集型,对于IO密集型任务,大部分时间都在等待IO操作完成,在等待时间中CPU是不需要工作的,即使提供多核CPU也利用不上

    网络爬虫属于IO密集型任务,发送网络请求等待响应、把爬取图片保存到本地,很多时间都消耗在等待中,如果启动多线程会明显提高效率

    改造一下上面的代码,由多进程爬虫改为多线程爬虫,如下

      1 # -*- coding:utf-8 -*-
      2 import requests
      3 from requests.exceptions import RequestException
      4 import os, time
      5 from lxml import etree
      6 import threading
      7 
      8 
      9 def get_html(url):
     10     response = requests.get(url, timeout=15)
     11     # print(response.status_code)
     12     try:
     13         if response.status_code == 200:
     14 
     15             # print(response.text)
     16             return response.text
     17         else:
     18              return None
     19     except RequestException:
     20         print("请求失败")
     21         # return None
     22 
     23 
     24 def parse_html(html_text):
     25     html = etree.HTML(html_text)
     26 
     27     if len(html) > 0:
     28         img_src = html.xpath("//img[@class='photothumb lazy']/@data-original")  # 元素提取方法
     29         # print(img_src)
     30         return img_src
     31 
     32     else:
     33         print("解析页面元素失败")
     34 
     35 def get_all_image_url(page_number):
     36     """
     37     获取所有图片的下载url
     38     :param page_number: 爬取页码
     39     :return: 所有图片url的集合
     40     """
     41 
     42     base_url = 'https://imgbin.com/free-png/naruto/'
     43     image_urls = []
     44 
     45     x = 1  # 定义一个标识,用于给每个图片url编号,从1递增
     46     for i in range(1, page_number):
     47         url = base_url + str(i)  # 根据页码遍历请求url
     48         try:
     49             html = get_html(url)  # 解析每个页面的内容
     50             if html:
     51                 data = parse_html(html)  # 提取页面中的图片url
     52                 # print(data)
     53                 # time.sleep(3)
     54                 if data:
     55                     for j in data:
     56                         image_urls.append({
     57                             'name': x,
     58                             'value': j
     59                         })
     60                         x += 1  # 每提取一个图片url,标识x增加1
     61         except RequestException as f:
     62             print("遇到错误:", f)
     63             continue
     64     # print(image_urls)
     65     return image_urls
     66 
     67 def get_image_content(url):
     68     """请求图片url,返回二进制内容"""
     69     # print("正在下载", url)
     70     try:
     71         r = requests.get(url, timeout=15)
     72         if r.status_code == 200:
     73             return r.content
     74         return None
     75     except RequestException:
     76         return None
     77 
     78 def main(url, image_name):
     79     """
     80     主函数:实现下载图片功能
     81     :param url: 图片url
     82     :param image_name: 图片名称
     83     :return:
     84     """
     85     print('当前子线程: {}'.format(threading.current_thread().name))
     86     save_path = os.path.dirname(os.path.abspath('.')) + '/pics/'
     87     try:
     88         file_path = '{0}/{1}.jpg'.format(save_path, image_name)
     89         if not os.path.exists(file_path):  # 判断是否存在文件,不存在则爬取
     90             with open(file_path, 'wb') as f:
     91                 f.write(get_image_content(url))
     92                 f.close()
     93 
     94                 print('第{}个文件保存成功'.format(image_name))
     95         else:
     96             print("第{}个文件已存在".format(image_name))
     97 
     98     except FileNotFoundError as f:
     99         print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))
    100         print("报错:", f)
    101         raise
    102 
    103     except TypeError as e:
    104         print("第{}个文件下载时遇到错误,url为:{}:".format(image_name, url))
    105         print("报错:", e)
    106 
    107 
    108 if __name__ == '__main__':
    109     start = time.time()
    110     print('这是主线程:{}'.format(threading.current_thread().name))
    111 
    112     urls = get_all_image_url(3)  # 获取所有图片url列表
    113     thread_list = []  # 定义一个列表,向里面追加线程
    114 
    115     for t in urls:
    116         # print(i)
    117         m = threading.Thread(target=main, args=(t["value"], t["name"]))  # 调用threading.Thread方法,传入主函数及其参数,启动多线程
    118 
    119         thread_list.append(m)
    120 
    121     for m in thread_list:
    122         m.start()  # 调用start()方法,开始执行
    123 
    124     for m in thread_list:
    125         m.join()  # 子线程调用join()方法,使主线程等待子线程运行完毕之后才退出
    126 
    127 
    128     end = time.time()
    129     print(end-start)

    同样爬取2页,因为有100张图片,所以一共启动了100个子线程,耗时大约6.5s

     如果打开文件夹来看的话,图片是一下子都出现的 

     

     通过对比,可以看到对于网络爬虫这种IO密集型任务,多线程的效率其实是比多进程高的(6.5s VS 29.9s)

    小结:本篇通过一个图片爬虫实例来说了一下如何使用python的多线程与多进程,对比单线程爬虫效率有明显提高,更多细节请自行查看,网上有很多优质资料,这里就不细说了

  • 相关阅读:
    写商业计划书的十个要点
    this page isn't working (ERR_EMPTY_RESPONSE)
    使用meta跳转页面
    fa-list-alt
    在linux下sh批处理文件调用java的方法
    产品使用的前后台框架API-dubbo-redis-elasticsearch-jquery
    JAVA虚拟机关闭钩子(Shutdown Hook)
    xxx is not in the sudoers file.This incident will be reported.的解决方法
    ssh连接docker容器
    namenode namespaceID与datanode namespaceID 不一致导致datanode无法启动的问题
  • 原文地址:https://www.cnblogs.com/hanmk/p/12747093.html
Copyright © 2011-2022 走看看