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

### 本文請先參考 前一則 &#x20;

[案例：pchome24h 爬蟲 - 搜索功能 多頁搜索](/requests/an-li-pchome24h-pa-chong-sou-suo-gong-neng-duo-ye-sou-suo.md)

### 為什麼要用多線程呢?

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

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

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

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

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

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

### 那會不會很花資源呢?

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

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

相關資訊：[什麼是多線程](/requests/shen-me-shi-duo-xian-cheng.md)

### 本篇

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

```
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的問題


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://water-1.gitbook.io/requests/an-li-pchome24h-pa-chong-sou-suo-gong-neng-duo-ye-sou-suo/an-li-pchome24h-pa-chong-sou-suo-gong-neng-duo-xian-cheng-pa-chong.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
