Python進(jìn)階多線程爬取網(wǎng)頁(yè)項(xiàng)目實(shí)戰(zhàn)
上一篇文章介紹了并發(fā)和多線程的概念,這次就來(lái)向大家上一個(gè)實(shí)戰(zhàn)來(lái)講解一下如何真正的運(yùn)用上多線程這個(gè)概念。
有需要的可以看看我之前這篇文章:Python進(jìn)階篇之多線程爬取網(wǎng)頁(yè)
一、網(wǎng)頁(yè)分析
這次我們選擇爬取的網(wǎng)站是水木社區(qū)的Python頁(yè)面
網(wǎng)頁(yè):https://www.mysmth.net/nForum/#!board/Python?p=1

根據(jù)慣例,我們第一步還是分析一下頁(yè)面結(jié)構(gòu)和翻頁(yè)時(shí)的請(qǐng)求。



通過(guò)前三頁(yè)的鏈接分析后得知,每一頁(yè)鏈接中最后的參數(shù)是頁(yè)數(shù),我們修改它即可得到其他頁(yè)面的數(shù)據(jù)。
再來(lái)分析一下,我們需要獲取帖子的鏈接就在id 為 body 的 section下,然后一層一層找到里面的 table,我們就能遍歷這些鏈接的標(biāo)題。

我們點(diǎn)開(kāi)一篇帖子:https://www.mysmth.net/nForum/#!article/Python/162717
和前面一樣,我們先分析標(biāo)題和內(nèi)容在網(wǎng)頁(yè)中的結(jié)構(gòu)
不難發(fā)現(xiàn),主題部分只要找到 id 為 main 的 section 下面的 class 為 b-head corner 的下面第二個(gè) span即可
主題部分

而內(nèi)容部分只要找到class 為 a-wrap corner 的 div,找到下面的 a-content即可。
內(nèi)容部分

分析網(wǎng)頁(yè)結(jié)構(gòu)后,我們就可以開(kāi)始寫(xiě)代碼了!
二、代碼實(shí)現(xiàn)
首先要確定要保存什么內(nèi)容:這次我們保存水木社區(qū) Python 版面前 10 頁(yè)的所有帖子標(biāo)題和帖子第一頁(yè)的所有回復(fù)。
解析列表頁(yè),得到所有的帖子鏈接
from bs4 import BeautifulSoup
# 解析列表頁(yè)內(nèi)容,得到這一頁(yè)的內(nèi)容鏈接
def parse_list_page(text):
soup = BeautifulSoup(text, 'html.parser')
# 下面相當(dāng)于 soup.find('table', class_='board-list tiz').find('tbody')
tbody = soup.find('table', class_='board-list tiz').tbody
urls = []
for tr in tbody:
td = tr.find('td', class_='title_9')
urls.append(td.a.attrs['href'])
return urls
解析內(nèi)容頁(yè),得到標(biāo)題和這一頁(yè)的所有帖子內(nèi)容
# 解析內(nèi)容頁(yè),得到標(biāo)題和所有帖子內(nèi)容
def parse_content_page(text):
soup = BeautifulSoup(text, 'html.parser')
title = soup.find('span', class_='n-left').text.strip('文章主題:').strip()
content_div = soup.find('div', class_='b-content corner')
contents = []
for awrap in content_div.find_all('div', class_='a-wrap corner'):
content = awrap.p.text
contents.append(content)
return title, contents
把列表頁(yè)的鏈接轉(zhuǎn)換成我們要抓取的鏈接
def convert_content_url(path): URL_PREFIX = 'http://www.mysmth.net' path += '?ajax' return URL_PREFIX + path
生成前 10 頁(yè)的列表頁(yè)鏈接
list_urls = [] for i in range(1, 11): url = 'http://www.mysmth.net/nForum/board/Python?ajax&p=' url += str(i) list_urls.append(url)
下面是得到前 10 頁(yè)列表頁(yè)里所有正文的鏈接。這個(gè)時(shí)候我們使用線程池的方式來(lái)運(yùn)行
import requests
from concurrent import futures
session = requests.Session()
executor = futures.ThreadPoolExecutor(max_workers=5)
# 這個(gè)函數(shù)獲取列表頁(yè)數(shù)據(jù),解析出鏈接,并轉(zhuǎn)換成真實(shí)鏈接
def get_content_urls(list_url):
res = session.get(list_url)
content_urls = parse_list_page(res.text)
real_content_urls = []
for url in content_urls:
url = convert_content_url(url)
real_content_urls.append(url)
return real_content_urls
# 根據(jù)剛剛生成的十個(gè)列表頁(yè)鏈接,開(kāi)始提交任務(wù)
fs = []
for list_url in list_urls:
fs.append(executor.submit(get_content_urls, list_url))
futures.wait(fs)
content_urls = set()
for f in fs:
for url in f.result():
content_urls.add(url)
在這里要注意一下,第 23 行中我們使用了 set() 函數(shù),作用是去除重復(fù)值。它的原理是創(chuàng)建一個(gè) Set(集合),集合 是 Python 中的一種特殊數(shù)據(jù)類型,其中可以包含多個(gè)元素,但是不能重復(fù)。我們來(lái)看看 set() 的用法
numbers = [1, 1, 2, 2, 2, 3, 4]
unique = set(numbers)
print(type(unique))
# 輸出:<class 'set'>
print(unique)
# 輸出:{1, 2, 3, 4}
我們看到,set() 將列表 numbers 轉(zhuǎn)換成了沒(méi)有重復(fù)元素的集合 {1, 2, 3, 4}。
我們利用這個(gè)特性,首先在 23 行通過(guò) content_urls = set() 創(chuàng)建了一個(gè)空集合,之后在其中添加鏈接時(shí),就會(huì)自動(dòng)去除多次出現(xiàn)的鏈接。
得到了所有正文鏈接之后,我們解析正文頁(yè)內(nèi)容,放到一個(gè)字典里
# 獲取正文頁(yè)內(nèi)容,解析出標(biāo)題和帖子
def get_content(url):
r = session.get(url)
title, contents = parse_content_page(r.text)
return title, contents
# 提交解析正文的任務(wù)
fs = []
for url in content_urls:
fs.append(executor.submit(get_content, url))
futures.wait(fs)
results = {}
for f in fs:
title, contents = f.result()
results[title] = contents
print(results.keys())
就這樣,我們完成了多線程的水木社區(qū)爬蟲(chóng)。打印 results.keys() 可以看到所有帖子的標(biāo)題。
這次爬取了前十頁(yè)的所有主題,以及他們的第一頁(yè)回復(fù)。一共 10 個(gè)列表頁(yè)、300 個(gè)主題頁(yè),解析出 1533 條回復(fù)。在一臺(tái)網(wǎng)絡(luò)良好、性能普通的機(jī)器上測(cè)試執(zhí)行只花費(fèi)了 13 秒左右。
完整代碼如下
import requests
from concurrent import futures
from bs4 import BeautifulSoup
# 解析列表頁(yè)內(nèi)容,得到這一頁(yè)的內(nèi)容鏈接
def parse_list_page(text):
soup = BeautifulSoup(text, 'html.parser')
tbody = soup.find('table', class_='board-list tiz').tbody
urls = []
for tr in tbody:
td = tr.find('td', class_='title_9')
urls.append(td.a.attrs['href'])
return urls
# 解析內(nèi)容頁(yè),得到標(biāo)題和所有帖子內(nèi)容
def parse_content_page(text):
soup = BeautifulSoup(text, 'html.parser')
title = soup.find('span', class_='n-left').text.strip('文章主題:').strip()
content_div = soup.find('div', class_='b-content corner')
contents = []
for awrap in content_div.find_all('div', class_='a-wrap corner'):
content = awrap.p.text
contents.append(content)
return title, contents
# 把列表頁(yè)得到的鏈接轉(zhuǎn)換成我們要抓取的鏈接
def convert_content_url(path):
URL_PREFIX = 'http://www.mysmth.net'
path += '?ajax'
return URL_PREFIX + path
# 生成前十頁(yè)的鏈接
list_urls = []
for i in range(1, 11):
url = 'http://www.mysmth.net/nForum/board/Python?ajax&p='
url += str(i)
list_urls.append(url)
session = requests.Session()
executor = futures.ThreadPoolExecutor(max_workers=5)
# 這個(gè)函數(shù)獲取列表頁(yè)數(shù)據(jù),解析出鏈接,并轉(zhuǎn)換成真實(shí)鏈接
def get_content_urls(list_url):
res = session.get(list_url)
content_urls = parse_list_page(res.text)
real_content_urls = []
for url in content_urls:
url = convert_content_url(url)
real_content_urls.append(url)
return real_content_urls
# 根據(jù)剛剛生成的十個(gè)列表頁(yè)鏈接,開(kāi)始提交任務(wù)
fs = []
for list_url in list_urls:
fs.append(executor.submit(get_content_urls, list_url))
futures.wait(fs)
content_urls = set()
for f in fs:
for url in f.result():
content_urls.add(url)
# 獲取正文頁(yè)內(nèi)容,解析出標(biāo)題和帖子
def get_content(url):
r = session.get(url)
title, contents = parse_content_page(r.text)
return title, contents
# 提交解析正文的任務(wù)
fs = []
for url in content_urls:
fs.append(executor.submit(get_content, url))
futures.wait(fs)
results = {}
for f in fs:
title, contents = f.result()
results[title] = contents
print(results.keys())
本次分享到此結(jié)束,謝謝大家閱讀??!
有問(wèn)題歡迎評(píng)論區(qū)留言?。?/p>
更多關(guān)于Python多線程爬取網(wǎng)頁(yè)實(shí)戰(zhàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Tensorflow進(jìn)行多維矩陣的拆分與拼接實(shí)例
今天小編就為大家分享一篇Tensorflow進(jìn)行多維矩陣的拆分與拼接實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-02-02
原來(lái)我一直安裝 Python 庫(kù)的姿勢(shì)都不對(duì)呀
平常我都是直接執(zhí)行 pip install 安裝的第三方庫(kù),很多教程也是這么介紹的,一直以來(lái)我都認(rèn)為這是標(biāo)準(zhǔn)的、正確的安裝 Python 第三方庫(kù)的姿勢(shì)。下面小編給大家分享一篇教程,一起看看吧2019-11-11
Python pip安裝第三方庫(kù)實(shí)現(xiàn)過(guò)程解析
這篇文章主要介紹了Python pip安裝第三方庫(kù)實(shí)現(xiàn)過(guò)程解析,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-07-07
Django?報(bào)錯(cuò):Broken?pipe?from?('127.0.0.1',?5892
這篇文章主要介紹了Django?報(bào)錯(cuò):Broken?pipe?from?('127.0.0.1',?58924)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-09-09
Python+PyQt5編寫(xiě)圖片格式轉(zhuǎn)換器
這篇文章主要為大家詳細(xì)介紹了如何利用Python和PyQt5編寫(xiě)一個(gè)簡(jiǎn)單的圖片格式轉(zhuǎn)換器,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以動(dòng)手嘗試一下2023-07-07
python模擬點(diǎn)擊在ios中實(shí)現(xiàn)的實(shí)例講解
在本篇文章里小編給大家整理的是一篇關(guān)于python模擬點(diǎn)擊在ios中實(shí)現(xiàn)的實(shí)例講解內(nèi)容,有需要的朋友們可以參考下。2020-11-11

