前言
在進(jìn)行爬蟲任務(wù)時(shí),我們常常會(huì)面臨兩個(gè)重要問題:如何提高爬取效率以及如何合理控制請求的并發(fā)量,以避免對目標(biāo)網(wǎng)站造成過大的壓力。針對這些問題,本文將介紹分布式爬蟲與并發(fā)控制的相關(guān)知識點(diǎn),并演示使用Scrapy框架實(shí)現(xiàn)分布式爬蟲,并對并發(fā)控制進(jìn)行限制請求頻率。
多線程與多進(jìn)程
當(dāng)涉及到并發(fā)處理時(shí),多線程和多進(jìn)程是兩種常用的技術(shù)。它們可以同時(shí)執(zhí)行多個(gè)任務(wù),提高程序的效率和性能。下面我將詳細(xì)講解多線程和多進(jìn)程的概念、特點(diǎn)以及使用方法。
多線程
多線程是指在一個(gè)進(jìn)程內(nèi)創(chuàng)建多個(gè)線程來執(zhí)行任務(wù)。線程是程序執(zhí)行中的最小單元,多個(gè)線程共享同一個(gè)進(jìn)程的資源。多線程有以下幾個(gè)特點(diǎn):
- 資源共享:多個(gè)線程可以共享同一個(gè)進(jìn)程的地址空間、文件描述符等資源,因此可以方便地進(jìn)行數(shù)據(jù)交換和通信。
- 輕量級:相對于多進(jìn)程而言,多線程的創(chuàng)建和切換成本更低,占用的系統(tǒng)資源更少。
- 異步編程:多線程可以實(shí)現(xiàn)異步操作,使得程序可以同時(shí)執(zhí)行多個(gè)任務(wù),提高程序的響應(yīng)速度。
在Python中,可以使用標(biāo)準(zhǔn)庫中的threading
模塊實(shí)現(xiàn)多線程編程。具體步驟如下:
- 導(dǎo)入
threading
模塊:import threading
- 定義線程函數(shù):編寫需要在每個(gè)線程中執(zhí)行的任務(wù)。
- 創(chuàng)建線程對象:使用
threading.Thread(target=函數(shù)名)
創(chuàng)建線程對象,并設(shè)置線程的目標(biāo)函數(shù)。 - 啟動(dòng)線程:使用
start()
方法啟動(dòng)線程,開始執(zhí)行線程中的任務(wù)。 - 等待線程結(jié)束:使用
join()
方法等待線程執(zhí)行完畢。
示例代碼:
import threading
def task():
# 線程執(zhí)行的任務(wù)
print("Thread ID: {} - Hello, World!".format(threading.get_ident()))
# 創(chuàng)建4個(gè)線程并啟動(dòng)
for _ in range(4):
thread = threading.Thread(target=task) # 創(chuàng)建線程對象,設(shè)置目標(biāo)函數(shù)為task
thread.start() # 啟動(dòng)線程
thread.join() # 等待線程結(jié)束
代碼使用threading模塊創(chuàng)建了4個(gè)線程,并在每個(gè)線程中執(zhí)行task()函數(shù)。每個(gè)線程將打印出當(dāng)前線程的ID,然后輸出"Hello, World!"。通過循環(huán)創(chuàng)建和啟動(dòng)線程,并使用join()方法等待線程結(jié)束,確保每個(gè)線程都執(zhí)行完畢。
多進(jìn)程
多進(jìn)程是指在操作系統(tǒng)中同時(shí)運(yùn)行多個(gè)進(jìn)程,每個(gè)進(jìn)程獨(dú)立執(zhí)行任務(wù)。不同進(jìn)程之間有自己獨(dú)立的內(nèi)存空間和資源環(huán)境,彼此之間不會(huì)相互影響。多進(jìn)程有以下幾個(gè)特點(diǎn):
- 數(shù)據(jù)隔離:每個(gè)進(jìn)程擁有獨(dú)立的地址空間和資源環(huán)境,數(shù)據(jù)之間相互隔離,保證了程序的安全性。
- 并發(fā)處理:多個(gè)進(jìn)程可以同時(shí)執(zhí)行不同的任務(wù),提高程序的并發(fā)處理能力。
- 穩(wěn)定性:由于進(jìn)程之間相互獨(dú)立,一個(gè)進(jìn)程崩潰不會(huì)影響其他進(jìn)程,提高了程序的穩(wěn)定性。
在Python中,可以使用標(biāo)準(zhǔn)庫中的multiprocessing
模塊實(shí)現(xiàn)多進(jìn)程編程。具體步驟如下:
- 導(dǎo)入
multiprocessing
模塊:import multiprocessing
- 定義進(jìn)程函數(shù):編寫需要在每個(gè)進(jìn)程中執(zhí)行的任務(wù)。
- 創(chuàng)建進(jìn)程對象:使用
multiprocessing.Process(target=函數(shù)名)
創(chuàng)建進(jìn)程對象,并設(shè)置進(jìn)程的目標(biāo)函數(shù)。 - 啟動(dòng)進(jìn)程:使用
start()
方法啟動(dòng)進(jìn)程,開始執(zhí)行進(jìn)程中的任務(wù)。 - 等待進(jìn)程結(jié)束:使用
join()
方法等待進(jìn)程執(zhí)行完畢。
示例代碼:
import multiprocessing
def task():
# 進(jìn)程執(zhí)行的任務(wù)
print("Process ID: {} - Hello, World!".format(multiprocessing.current_process().pid))
# 創(chuàng)建4個(gè)進(jìn)程并啟動(dòng)
processes = []
for _ in range(4):
process = multiprocessing.Process(target=task) # 創(chuàng)建進(jìn)程對象,設(shè)置目標(biāo)函數(shù)為task
process.start() # 啟動(dòng)進(jìn)程
processes.append(process)
# 等待所有進(jìn)程結(jié)束
for process in processes:
process.join()
代碼使用multiprocessing模塊創(chuàng)建了4個(gè)進(jìn)程,并在每個(gè)進(jìn)程中執(zhí)行task()函數(shù)。每個(gè)進(jìn)程將打印出當(dāng)前進(jìn)程的ID,然后輸出"Hello, World!"。通過循環(huán)創(chuàng)建和啟動(dòng)進(jìn)程,并使用join()方法等待所有進(jìn)程結(jié)束,確保每個(gè)進(jìn)程都執(zhí)行完畢。
注意,在多進(jìn)程示例中,我們使用了一個(gè)列表來保存所有的進(jìn)程對象,然后在最后使用循環(huán)和join()方法等待所有進(jìn)程結(jié)束。
多線程和多進(jìn)程的選擇
在實(shí)際開發(fā)中,選擇多線程還是多進(jìn)程需要根據(jù)具體的需求和情況進(jìn)行權(quán)衡。一般來說:
- 如果任務(wù)涉及到I/O密集型操作(如網(wǎng)絡(luò)請求、文件讀寫等),多線程通常是一個(gè)不錯(cuò)的選擇,因?yàn)榫€程之間可以很好地共享資源和交互。
- 如果任務(wù)涉及到CPU密集型操作(如復(fù)雜的計(jì)算、圖像處理等),多進(jìn)程可能更適合,因?yàn)槊總€(gè)進(jìn)程有獨(dú)立的計(jì)算資源,可以充分利用多核處理器。
此外,需要注意的是,多線程和多進(jìn)程的并發(fā)操作涉及到共享資源的訪問,可能引發(fā)一些并發(fā)控制和同步機(jī)制的問題,例如線程安全性、鎖、信號量等,開發(fā)者需要注意處理這些問題,保證程序的正確執(zhí)行。
使用Scrapy框架實(shí)現(xiàn)分布式爬蟲
Scrapy是一個(gè)強(qiáng)大的Python爬蟲框架,提供了分布式爬蟲的支持。通過使用Scrapy的分布式架構(gòu),我們可以將爬取任務(wù)分發(fā)到多個(gè)節(jié)點(diǎn)上,以提高爬取效率。
- 當(dāng)使用Scrapy框架實(shí)現(xiàn)分布式爬蟲時(shí),可以利用Scrapy-Redis擴(kuò)展來實(shí)現(xiàn)任務(wù)隊(duì)列的管理和分布式爬取。Scrapy-Redis擴(kuò)展通過使用Redis作為任務(wù)隊(duì)列實(shí)現(xiàn)多個(gè)爬蟲節(jié)點(diǎn)之間的任務(wù)調(diào)度和數(shù)據(jù)共享。下
首先安裝好Scrapy和Scrapy-Redis擴(kuò)展。
命令如下:
pip install scrapy scrapy-redis
接下來,創(chuàng)建一個(gè)Scrapy項(xiàng)目,并在該項(xiàng)目中進(jìn)行相應(yīng)的配置。
1. 創(chuàng)建Scrapy項(xiàng)目
scrapy startproject myproject
這會(huì)生成一個(gè)名為
myproject
的Scrapy項(xiàng)目。
2. 配置Scrapy-Redis
進(jìn)入項(xiàng)目目錄,打開settings.py
文件,添加以下內(nèi)容:
# 開啟Scrapy-Redis擴(kuò)展
import scrapy_redis
# 將默認(rèn)的Scheduler替換為Scrapy-Redis提供的Scheduler
SCHEDULER = "scrapy_redis.scheduler.Scheduler"
# 使用Redis的去重器
DUPEFILTER_CLASS = "scrapy_redis.dupefilter.RFPDupeFilter"
# 設(shè)置Redis為隊(duì)列的存儲(chǔ)方式
SCHEDULER_QUEUE_CLASS = "scrapy_redis.queue.SpiderPriorityQueue"
# 允許暫停后,恢復(fù)之前的爬取進(jìn)度
SCHEDULER_PERSIST = True
# 在Redis中保留請求隊(duì)列和去重集合的時(shí)間,單位為秒
SCHEDULER_IDLE_BEFORE_CLOSE = 10
# 設(shè)置Redis連接參數(shù)
REDIS_HOST = 'localhost' # Redis服務(wù)器地址
REDIS_PORT = 6379 # Redis端口號
# 啟用RedisPipeline將數(shù)據(jù)存儲(chǔ)到Redis中
ITEM_PIPELINES = {
'scrapy_redis.pipelines.RedisPipeline': 300,
}
以上配置啟用了Scrapy-Redis擴(kuò)展并設(shè)置了Redis的連接參數(shù),指定了使用Redis作為隊(duì)列的存儲(chǔ)方式,并使用RedisPipeline將數(shù)據(jù)存儲(chǔ)到Redis中。
3. 創(chuàng)建爬蟲
進(jìn)入項(xiàng)目目錄,在命令行中運(yùn)行以下命令創(chuàng)建一個(gè)Spider:
cd myproject
scrapy genspider example example.com
這將在spiders
目錄下生成一個(gè)名為example.py
的Spider文件。
打開example.py
文件,將其內(nèi)容修改為以下代碼:
import scrapy
from scrapy_redis.spiders import RedisSpider
class ExampleSpider(RedisSpider):
name = 'example'
allowed_domains = ['example.com']
def parse(self, response):
self.logger.info('Crawled (%d) %s' % (response.status, response.url))
# 在這里編寫你的解析邏輯
# ...
yield {
'url': response.url,
# ...
}
這個(gè)Spider繼承自RedisSpider
,使得它能夠與Scrapy-Redis擴(kuò)展配合工作。在parse
函數(shù)中,你可以編寫你的解析邏輯,解析頁面中的數(shù)據(jù)。
4. 啟動(dòng)爬蟲節(jié)點(diǎn)
在命令行中運(yùn)行以下命令啟動(dòng)爬蟲節(jié)點(diǎn):
scrapy crawl example
此時(shí),爬蟲節(jié)點(diǎn)會(huì)連接到Redis隊(duì)列,并開始從隊(duì)列中獲取任務(wù)并執(zhí)行。
5. 添加任務(wù)到隊(duì)列
最后,可以通過將任務(wù)添加到Redis隊(duì)列來分發(fā)給爬蟲節(jié)點(diǎn)。可以使用以下代碼將任務(wù)添加到隊(duì)列中:
import redis
import json
# 連接到Redis
redis_client = redis.Redis(host='localhost', port=6379)
# 添加任務(wù)到隊(duì)列
task = {
'url': 'http://www.example.com',
# ...
}
redis_client.lpush('example:start_urls', json.dumps(task))
代碼通過Redis的連接客戶端redis.Redis
連接到Redis服務(wù)器,并將任務(wù)封裝為JSON格式的字符串,然后使用lpush()
方法將任務(wù)添加到名為example:start_urls
的隊(duì)列中。
通過以上步驟,你就可以實(shí)現(xiàn)使用Scrapy框架和Scrapy-Redis擴(kuò)展來實(shí)現(xiàn)分布式爬蟲。每個(gè)爬蟲節(jié)點(diǎn)都可以從Redis隊(duì)列中獲取任務(wù),并將結(jié)果存儲(chǔ)到Redis中,實(shí)現(xiàn)數(shù)據(jù)的共享和分布式爬取。
并發(fā)控制與限制請求頻率
當(dāng)進(jìn)行爬蟲開發(fā)時(shí),為了避免對目標(biāo)網(wǎng)站造成過大的壓力或觸發(fā)反爬措施,我們通常需要對并發(fā)請求數(shù)量進(jìn)行控制,并限制請求頻率。
并發(fā)控制
并發(fā)控制是指控制同時(shí)發(fā)送給目標(biāo)網(wǎng)站的請求數(shù)量,以避免對其服務(wù)器造成過大的負(fù)載。Scrapy提供了幾種方式來實(shí)現(xiàn)并發(fā)控制:
-
在
settings.py
中設(shè)置CONCURRENT_REQUESTS
參數(shù)來控制同時(shí)發(fā)送的請求數(shù)量。例如,可以將其設(shè)置為CONCURRENT_REQUESTS = 16
。CONCURRENT_REQUESTS = 16
-
可以使用
CONCURRENT_REQUESTS_PER_DOMAIN
參數(shù)來限制每個(gè)域名同時(shí)發(fā)送的請求數(shù)量。例如,可以將其設(shè)置為CONCURRENT_REQUESTS_PER_DOMAIN = 8
。CONCURRENT_REQUESTS_PER_DOMAIN = 8
-
還可以使用
CONCURRENT_REQUESTS_PER_IP
參數(shù)來限制每個(gè)IP地址同時(shí)發(fā)送的請求數(shù)量。例如,可以將其設(shè)置為CONCURRENT_REQUESTS_PER_IP = 4
。CONCURRENT_REQUESTS_PER_IP = 4
注意:在設(shè)置并發(fā)控制參數(shù)時(shí),要根據(jù)目標(biāo)網(wǎng)站的承受能力和自身的網(wǎng)絡(luò)帶寬來合理調(diào)整,避免給目標(biāo)網(wǎng)站和自己帶來不必要的困擾。
限制請求頻率
限制請求頻率是指控制發(fā)送請求的時(shí)間間隔,以避免短時(shí)間內(nèi)發(fā)送過多的請求。Scrapy提供了幾種方式來實(shí)現(xiàn)請求頻率限制:
-
可以在Spider中使用
download_delay
屬性來設(shè)置每個(gè)請求之間的時(shí)間間隔(單位為秒)。class MySpider(scrapy.Spider): name = 'example' allowed_domains = ['example.com'] start_urls = ['http://www.example.com'] download_delay = 3 # 設(shè)置下載延遲為3秒 def parse(self, response): # 解析頁面數(shù)據(jù) pass
-
還可以在Spider中重寫
start_requests()
方法,在方法中使用time.sleep()
方法來控制請求的時(shí)間間隔。import time class MySpider(scrapy.Spider): name = 'example' allowed_domains = ['example.com'] start_urls = ['http://www.example.com'] def start_requests(self): for url in self.start_urls: time.sleep(3) # 每個(gè)請求之間等待3秒 yield scrapy.Request(url=url, callback=self.parse) def parse(self, response): # 解析頁面數(shù)據(jù) pass
-
可以使用
AUTOTHROTTLE_ENABLED
參數(shù)啟用自動(dòng)限速擴(kuò)展。AUTOTHROTTLE_ENABLED = True
以上是實(shí)現(xiàn)并發(fā)控制和請求頻率限制的幾種方式文章來源:http://www.zghlxwxcb.cn/news/detail-518796.html
未完待續(xù)…
文章來源地址http://www.zghlxwxcb.cn/news/detail-518796.html
到了這里,關(guān)于爬蟲入門指南(5): 分布式爬蟲與并發(fā)控制 【提高爬取效率與請求合理性控制的實(shí)現(xiàn)方法】的文章就介紹完了。如果您還想了解更多內(nèi)容,請?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!