python面向?qū)ο蠖嗑€(xiàn)程爬蟲(chóng)爬取搜狐頁(yè)面的實(shí)例代碼
首先我們需要幾個(gè)包:requests, lxml, bs4, pymongo, redis
1. 創(chuàng)建爬蟲(chóng)對(duì)象,具有的幾個(gè)行為:抓取頁(yè)面,解析頁(yè)面,抽取頁(yè)面,儲(chǔ)存頁(yè)面
class Spider(object): def __init__(self): # 狀態(tài)(是否工作) self.status = SpiderStatus.IDLE # 抓取頁(yè)面 def fetch(self, current_url): pass # 解析頁(yè)面 def parse(self, html_page): pass # 抽取頁(yè)面 def extract(self, html_page): pass # 儲(chǔ)存頁(yè)面 def store(self, data_dict): pass
2. 設(shè)置爬蟲(chóng)屬性,沒(méi)有在爬取和在爬取中,我們用一個(gè)類(lèi)封裝, @unique使里面元素獨(dú)一無(wú)二,Enum和unique需要從 enum里面導(dǎo)入:
@unique class SpiderStatus(Enum): IDLE = 0 WORKING = 1
3. 重寫(xiě)多線(xiàn)程的類(lèi):
class SpiderThread(Thread): def __init__(self, spider, tasks): super().__init__(daemon=True) self.spider = spider self.tasks = tasks def run(self): while True: pass
4. 現(xiàn)在爬蟲(chóng)的基本結(jié)構(gòu)已經(jīng)做完了,在main函數(shù)創(chuàng)建tasks, Queue需要從queue里面導(dǎo)入:
def main(): # list沒(méi)有鎖,所以使用Queue比較安全, task_queue=[]也可以使用,Queue 是先進(jìn)先出結(jié)構(gòu), 即 FIFO task_queue = Queue() # 往隊(duì)列放種子url, 即搜狐手機(jī)端的url task_queue.put('http://m.sohu,com/') # 指定起多少個(gè)線(xiàn)程 spider_threads = [SpiderThread(Spider(), task_queue) for _ in range(10)] for spider_thread in spider_threads: spider_thread.start() # 控制主線(xiàn)程不能停下,如果隊(duì)列里有東西,任務(wù)不能停, 或者spider處于工作狀態(tài),也不能停 while task_queue.empty() or is_any_alive(spider_threads): pass print('Over')
4-1. 而 is_any_threads則是判斷線(xiàn)程里是否有spider還活著,所以我們?cè)賹?xiě)一個(gè)函數(shù)來(lái)封裝一下:
def is_any_alive(spider_threads): return any([spider_thread.spider.status == SpiderStatus.WORKING for spider_thread in spider_threads])
5. 所有的結(jié)構(gòu)已經(jīng)全部寫(xiě)完,接下來(lái)就是可以填補(bǔ)爬蟲(chóng)部分的代碼,在SpiderThread(Thread)
里面,開(kāi)始寫(xiě)爬蟲(chóng)運(yùn)行 run 的方法,即線(xiàn)程起來(lái)后,要做的事情:
def run(self): while True: # 獲取url current_url = self.tasks_queue.get() visited_urls.add(current_url) # 把爬蟲(chóng)的status改成working self.spider.status = SpiderStatus.WORKING # 獲取頁(yè)面 html_page = self.spider.fetch(current_url) # 判斷頁(yè)面是否為空 if html_page not in [None, '']: # 去解析這個(gè)頁(yè)面, 拿到列表 url_links = self.spider.parse(html_page) # 把解析完的結(jié)構(gòu)加到 self.tasks_queue里面來(lái) # 沒(méi)有一次性添加到隊(duì)列的方法 用循環(huán)添加算求了 for url_link in url_links: self.tasks_queue.put(url_link) # 完成任務(wù),狀態(tài)變回IDLE self.spider.status = SpiderStatus.IDLE
6. 現(xiàn)在可以開(kāi)始寫(xiě) Spider()這個(gè)類(lèi)里面的四個(gè)方法,首先寫(xiě)fetch()抓取頁(yè)面里面的:
@Retry() def fetch(self, current_url, *, charsets=('utf-8', ), user_agent=None, proxies=None): thread_name = current_thread().name print(f'[{thread_name}]: {current_url}') headers = {'user-agent': user_agent} if user_agent else {} resp = requests.get(current_url, headers=headers, proxies=proxies) # 判斷狀態(tài)碼,只要200的頁(yè)面 return decode_page(resp.content, charsets) \ if resp.status_code == 200 else None
6-1. decode_page是我們?cè)陬?lèi)的外面封裝一個(gè)解碼的函數(shù):
def decode_page(page_bytes, charsets=('utf-8',)): page_html = None for charset in charsets: try: page_html = page_bytes.decode(charset) break except UnicodeDecodeError: pass # logging.error('Decode:', error) return page_html
6-2. @retry是裝飾器,用于重試, 因?yàn)樾枰獋鲄?,在這里我們用一個(gè)類(lèi)來(lái)包裝, 所以最后改成@Retry():
# retry的類(lèi),重試次數(shù)3次,時(shí)間5秒(這樣寫(xiě)在裝飾器就不用傳參數(shù)類(lèi)), 異常 class Retry(object): def __init__(self, *, retry_times=3, wait_secs=5, errors=(Exception, )): self.retry_times = retry_times self.wait_secs = wait_secs self.errors = errors # call 方法傳參 def __call__(self, fn): def wrapper(*args, **kwargs): for _ in range(self.retry_times): try: return fn(*args, **kwargs) except self.errors as e: # 打日志 logging.error(e) # 最小避讓 self.wait_secs 再發(fā)起請(qǐng)求(最小避讓時(shí)間) sleep((random() + 1) * self.wait_secs) return None return wrapper()
7. 接下來(lái)寫(xiě)解析頁(yè)面的方法,即 parse():
# 解析頁(yè)面 def parse(self, html_page, *, domain='m.sohu.com'): soup = BeautifulSoup(html_page, 'lxml') url_links = [] # 找body的有 href 屬性的 a 標(biāo)簽 for a_tag in soup.body.select('a[href]'): # 拿到這個(gè)屬性 parser = urlparse(a_tag.attrs['href']) netloc = parser.netloc or domain scheme = parser.scheme or 'http' netloc = parser.netloc or 'm.sohu.com' # 只爬取 domain 底下的 if scheme != 'javascript' and netloc == domain: path = parser.path query = '?' + parser.query if parser.query else '' full_url = f'{scheme}://{netloc}{path}{query}' if full_url not in visited_urls: url_links.append(full_url) return url_links
7-1. 我們需要在SpiderThread()的 run方法里面,在
current_url = self.tasks_queue.get()
下面添加
visited_urls.add(current_url)
在類(lèi)外面再添加一個(gè)
visited_urls = set()去重
8. 現(xiàn)在已經(jīng)能開(kāi)始抓取到相應(yīng)的網(wǎng)址。
總結(jié)
以上所述是小編給大家介紹的python面向?qū)ο蠖嗑€(xiàn)程爬蟲(chóng)爬取搜狐頁(yè)面的實(shí)例代碼,希望對(duì)大家有所幫助,如果大家有任何疑問(wèn)請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
相關(guān)文章
使用Python pandas讀取CSV文件應(yīng)該注意什么?
本文是給使用pandas的新手而寫(xiě),主要列出一些常見(jiàn)的問(wèn)題,根據(jù)筆者所踩過(guò)的坑,進(jìn)行歸納總結(jié),希望對(duì)讀者有所幫助,需要的朋友可以參考下2021-06-06python讀取文件列表并排序的實(shí)現(xiàn)示例
本文主要介紹了python讀取文件列表并排序的實(shí)現(xiàn)示例,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07

python?pygame實(shí)現(xiàn)控制物體移動(dòng)

Python使用擴(kuò)展庫(kù)pywin32實(shí)現(xiàn)批量文檔打印實(shí)例

Python基于Webhook實(shí)現(xiàn)github自動(dòng)化部署

Django import export實(shí)現(xiàn)數(shù)據(jù)庫(kù)導(dǎo)入導(dǎo)出方式

python基于xml parse實(shí)現(xiàn)解析cdatasection數(shù)據(jù)

Python中asyncore異步模塊的用法及實(shí)現(xiàn)httpclient的實(shí)例