案例:pchome24h 爬蟲 - 搜索功能 多線程爬蟲

本文請先參考 前一則

案例:pchome24h 爬蟲 - 搜索功能 多頁搜索

為什麼要用多線程呢?

假設我們的工作量一共有100頁,每爬行一頁就需要1秒鐘

那麼如果你要爬完 理論時間是 100秒(約二分鐘),聽起還可以接受

那麼5000頁呢?那就是5000秒(83分鐘),在大量的頁面爬行中,只有一個線程,

可以理解成一個循環幹到底,是不切實際的…

所以這時候多線程的功效就出來了,從一個機器人員工變為100個機器人員工

5000頁的工作 花費的時間理論值會變為 5000/100/60 = 50/60 約1分鐘內搞定

那會不會很花資源呢?

完全不會,以我自己在做測試的小型文書機電腦(八年前汰舊下來的),在跑爬蟲的時候,可以用到5000個線程全速前進!

詳細的資源計算,就不在此詳述,總之很夠用!

相關資訊:什麼是多線程

本篇

在上一篇 多頁搜索的程式碼最後是長這樣

import requests #先引用requests
import time

#目標網址為:https://ecshweb.pchome.com.tw/search/v3.3/all/results?q=關鍵字
#請求範例:https://ecshweb.pchome.com.tw/search/v3.3/all/results?q=%E8%97%8D%E8%8A%BD%E9%9F%B3%E9%9F%BF&page=1&sort=sale/dc

#建構網址
keyword = "藍芽音響"
url = "https://ecshweb.pchome.com.tw/search/v3.3/all/results?q="+keyword+"&page=1&sort=sale/dc"

#嘗試請求
rs = requests.get(url=url)

#輸出結果
data = rs.text
print(data)

import json #引用json函式

json_data = json.loads(data)
page_max = json_data['totalPage'] #取得所有頁數

all_items = [] #先宣告一個 所有商品的保存陣列




for n in range(page_max): #針對頁數做一個迴圈
    page_num = n + 1 #因為 頁碼會從0開始計算
    if page_num > 20: #只爬到第20頁就停止,本次教學 不含換ip教學 所以先這樣
        break

    # 建構網址
    #將頁碼與字串合併,因為 頁數為 整數int型別,所以要括一個format來進行轉換
    url = "https://ecshweb.pchome.com.tw/search/v3.3/all/results?q=" + keyword + "&page="+format(page_num)+"&sort=sale/dc"
    # 嘗試請求
    rs = requests.get(url=url)
    # 輸出結果
    data = rs.text
    try:#做一個例外處理,如果出現錯誤可以避免卡住
        page_data = json.loads(data)#轉換json格式
        products = page_data['prods']#取得商品列表
        for pro in products:
            all_items.append(pro)#將商品保存進商品陣列
        # print(data)
        print("目前進度頁數 {}/{} 已保存:{}個商品".format(page_num,page_max,len(all_items)))#字串整合的應用
    except Exception as e: #輸出的錯誤名稱叫e
        print(e) #印出錯誤內容
        print(data) #印出這一頁所抓取的值

    time.sleep(1)#記得要休息一秒再抓取,不然會被封ip

#保存結果
all_items_json = json.dumps(all_items) #將資料轉為json格式字串
fp = open("result.txt",'w',encoding="utf-8")#建立檔案
fp.write(all_items_json)#寫入
fp.close()#關閉檔案

那麼我們要編輯的位置27行開始 ,將工作的方式從一個迴圈做到底 改為多個線程做到底。

1. 引用 隊列(佇列、線程池、進程池)

在Python 中 有一個公有的引用模組,叫queue(注意一定要全小寫)這個模組

專門做為任務分發用的模組,用比較好記的方式來描述功能就是

queue像是排隊看電影進場前的服務人員,負責發放3D眼鏡,驗票,放人進去用的。

這個功能其實也可以自己做,不過既然queue已經做好了就直接就用吧!

常用的queue功能

queue.get() 取得一個任務 # 抓出一個觀眾
queue.put() 插入一個任務 # 有一個觀眾隊伍領號碼牌
queue.qsize() 計算隊伍有多長

基本上只會用到這三個功能
Que = queue.Queue() #初始化隊伍

#將所有的頁數,分發進入隊伍
for n in range(page_max): #針對頁數做一個迴圈
    page_num = n + 1 #因為 頁碼會從0開始計算
    Que.put(page_num) #將頁碼送進去隊伍

2. 初始化線程(執行緒) Thread

Python 提供了公用的模組 叫 threading 可以進行線程的開啟引用

新手要注意的幾點如下

  • 線程的引用是從 threading裡面的Thread來使用,不是直接用 threading

from threading import Thread
  • 線程啟用後,要記得啟動它start()如

t = Thread(target="要觸發的函數") #target這個參數為你要多線程運作的函式
t.start() #一定要有start,不然線程會不工作
  • 每個線程 都記得要告訴它 是負責運作什麼函式,也就是說 target這個參數記得要填,新手很容易沒想清楚,以為是一種魔法般的功能沒給它函式就使用

最終程式碼


def Run_24hshop_Page(): #定義要做為 Thread函式的 函式
    global all_items #確保這裡的 all_items收集陣列為全域的
    while Que.qsize() > 1: #因為是專門為消化隊伍所使用,所以在隊伍沒有結束之前 都要不斷運作著
        page_num = Que.get() #用get取得要爬的頁數
        #以下參考 前一篇的單線程爬蟲
        url = "https://ecshweb.pchome.com.tw/search/v3.3/all/results?q=" + keyword + "&page=" + format(
            page_num) + "&sort=sale/dc"
        # 嘗試請求
        rs = requests.get(url=url)
        # 輸出結果
        data = rs.text
        try:  # 做一個例外處理,如果出現錯誤可以避免卡住
            page_data = json.loads(data)  # 轉換json格式
            products = page_data['prods']  # 取得商品列表
            for pro in products:
                all_items.append(pro)  # 將商品保存進商品陣列
            print("頁數:{} 爬取成功!".format(page_num))
        except Exception as e:  # 輸出的錯誤名稱叫e
            # print()
            print("頁數:{} 爬取失敗 被擋ip".format(page_num))
            # print(e)  # 印出錯誤內容
            # print(data)  # 印出這一頁所抓取的值

        time.sleep(1)  # 記得要休息一秒再抓取,不然會被封ip

#運作Thread
#每初始化一個線程就代表一個線程的產生
#所以要產生如 10 個線程 那就直接用迴圈產生30次就好了

Thread_Team = []# 【線程工作收集陣列】 用來收集工作中的線程好確認是不是都工作完成
for x in range(10):
    t = Thread(target=Run_24hshop_Page) #target這個參數為你要多線程運作的函式
    t.start() #一定要有start,不然線程會不工作
    Thread_Team.append(t) #要記得將線程加入到上面準備好的 【線程工作收集陣列】
#等待所有線程工作完成
for t in Thread_Team:
    t.join() #join這個功能代表,等待 線程工作結束,如果沒有結束就會一直卡在這裡,透過這個特性,來完成等待任務完工

預期的輸出

初始化線程完成 一共 10 個線程 
頁數:3 爬取成功!
頁數:2 爬取成功!
頁數:9 爬取成功!
頁數:4 爬取成功!
頁數:5 爬取成功!
頁數:1 爬取成功!
頁數:7 爬取成功!
頁數:8 爬取成功!
頁數:10 爬取成功!
頁數:6 爬取成功!
頁數:11 爬取成功!
頁數:12 爬取成功!
頁數:14 爬取成功!
頁數:16 爬取成功!
頁數:13 爬取成功!
頁數:17 爬取成功!
頁數:18 爬取成功!
頁數:19 爬取成功!
頁數:15 爬取成功!
頁數:20 爬取成功!
頁數:21 爬取成功!
頁數:22 爬取成功!
頁數:24 爬取成功!
頁數:26 爬取成功!
頁數:23 爬取成功!
頁數:25 爬取成功!
頁數:27 爬取失敗 被擋ip
頁數:28 爬取成功!
頁數:30 爬取成功!
頁數:29 爬取成功!
頁數:31 爬取失敗 被擋ip
頁數:32 爬取成功!
頁數:34 爬取失敗 被擋ip
頁數:35 爬取失敗 被擋ip

會看到爬取35頁的速度非常之快,不過在27頁開始,就出現被擋ip的訊息

多線程爬行的方案,到這裡就算告一段落

下回再來解說,如果解決擋ip的問題

Last updated