多線程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的多線程與多進程,對比單線程爬蟲效率有明顯提高,更多細節請自行查看,網上有很多優質資料,這里就不細說了

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM