本文旨在科普,用最簡單明了的語言引導(dǎo)初學(xué)者接觸爬蟲,不求嚴(yán)謹(jǐn)??吹讲欢幕蛘卟幌攵膶S忻~不要糾結(jié),只要代碼沒問題就繼續(xù)下去,等爬取成功了再回頭摳細(xì)節(jié)。
我將以虎撲火箭專區(qū)為例,爬取火區(qū)前一百頁的標(biāo)題。
(實(shí)在不喜歡火箭的可以用其它球隊(duì)哈)
最先要說明的是,Python爬蟲非常簡單?。ó?dāng)然是指入門)
好了,直入主題吧!
導(dǎo)入三個(gè)模塊
requests 用來發(fā)送請求(發(fā)送了請求,服務(wù)器才會(huì)響應(yīng)你,你的瀏覽器才有內(nèi)容顯示)
pyquery 用來解析頁面
fake_useragent 用來偽造請求頭(相當(dāng)于身份證)
import requests
from pyquery import PyQuery as pq
from fake_useragent import UserAgent
第一步 爬取
火箭專區(qū)地址:https://bbs.hupu.com/rockets
我們直接用requests發(fā)送get請求
url = 'https://bbs.hupu.com/rockets'
r = requests.get(url)
print(r)

這個(gè)r就是HTTP狀態(tài)碼,我們最常見的404也是狀態(tài)碼

print(r.text)

爬取頁面就搞定了,不過出來的都是源碼,我們要篩選出想要的信息
第二步 解析
要解析頁面,必須先要了解一下頁面的結(jié)構(gòu),我們先去火箭專區(qū),然后右鍵第一個(gè)標(biāo)題,檢查(以chrome瀏覽器為例,360瀏覽器好像是審查元素)

Elements面板下展示的內(nèi)容,就是網(wǎng)頁的DOM節(jié)點(diǎn)和CSS樣式,簡單說就是網(wǎng)頁的結(jié)構(gòu)和內(nèi)容,按小三角可以折疊和展開,鼠標(biāo)放在代碼上面,左邊會(huì)高亮對應(yīng)的內(nèi)容,雙擊代碼可以直接編輯。

...
這樣的叫做標(biāo)簽
class='for-list'
這樣帶等號(hào)的叫做屬性,有點(diǎn)像python的字典,等號(hào)左邊是屬性名,右邊是屬性值
class屬性是非常常用的屬性,也叫做類名
經(jīng)過觀察,我們會(huì)發(fā)現(xiàn)每個(gè)帖子信息都在li標(biāo)簽里面
而標(biāo)題內(nèi)容都在class屬性為truetit的a標(biāo)簽里面

下面簡單粗暴地說下pyquery的語法(細(xì)講的話要另起一文了)
標(biāo)簽直接寫,
class屬性前面加個(gè)點(diǎn),
id屬性前面加個(gè)#
空格表示嵌套關(guān)系(也就是子孫節(jié)點(diǎn))
(結(jié)合下面代碼理解)
#初始化,先讓pyquery認(rèn)識(shí)它,才能解析它
doc = pq(r.text)
#class屬性為for-list的ul標(biāo)簽 下面的li標(biāo)簽
#因?yàn)槲覀円謩e取出每個(gè)li標(biāo)簽的內(nèi)容,所以要加上items方法方便遍歷
info = doc('ul.for-list li').items()#
#對每個(gè)li標(biāo)簽,提取它嵌套的class屬性為truetit的a標(biāo)簽的內(nèi)容
#獲取文本用text方法
for i in info:
title = i('a.truetit').text()
print(title)

成功了!
既然成功爬取了第一頁,我們就試試爬前十頁
要有網(wǎng)址才能爬取,所以我們先要解決網(wǎng)址的問題,常規(guī)思路是提取網(wǎng)站下一頁的鏈接,但是虎撲的下一頁其實(shí)是javascript渲染的。而我們用requests直接發(fā)送get請求得到的是未經(jīng)渲染的原始信息。js渲染這種高級(jí)的東西我們目前無法解決,但我們通過點(diǎn)擊下一頁網(wǎng)址可以發(fā)現(xiàn)
第一頁:https://bbs.hupu.com/rockets
第二頁:https://bbs.hupu.com/rockets-2
第三頁:https://bbs.hupu.com/rockets-3
似乎橫杠后面的數(shù)字就是頁數(shù)
我們試試-1,-10能不能轉(zhuǎn)到第一頁和第十頁,結(jié)果是能的
所以我們就通過這個(gè)取巧的方法得到了前十頁的網(wǎng)址
把我們之前的代碼封裝成函數(shù)
def get_title(n):
url = f'https://bbs.hupu.com/rockets-{n}'
r = requests.get(url)
doc = pq(r.text)
info = doc('ul.for-list li').items()
for i in info:
title = i('a.truetit').text()
print(title)
然后在1-10遍歷一下
for i in range(11):
get_title(i)
十頁標(biāo)題就嘩嘩嘩地爬出來了
但我們的目標(biāo)是要爬100頁的
照葫蘆畫瓢把range(11)改成range(101)不就可以了?
*for i in range(11):
get_title(i)
事實(shí)證明是不行的,出來的還是前十頁的標(biāo)題,那問題出在哪呢?
之前我們得到結(jié)論,橫杠后面的就是頁數(shù),難道第十頁之后網(wǎng)址變了?
于是我們打開瀏覽器,試著打開https://bbs.hupu.com/rockets-20
發(fā)現(xiàn)瀏覽器跳轉(zhuǎn)到了登錄界面,也就是說不登錄是看不到10頁以后的內(nèi)容的
所以我們就要涉及到一個(gè)爬蟲的基礎(chǔ)知識(shí)了:headers(頭部信息)
打開https://bbs.hupu.com/rockets-10
右鍵檢查,進(jìn)入network面板,這里記錄了從發(fā)起網(wǎng)頁頁面請求后,分析HTTP請求后得到的各個(gè)請求資源信息
找到rockets-10,右邊的headers面板記錄了頭部信息

General 概況,重點(diǎn):請求地址,方法,狀態(tài)
Response Headers 響應(yīng)頭,重點(diǎn):響應(yīng)內(nèi)容類型
Request Headers 請求頭 重點(diǎn): user-agent,cookie
一般來說,構(gòu)建headers的時(shí)候,user-agent是必要的,相當(dāng)于你的身份信息,告訴服務(wù)器你是誰
所以我們之前的爬蟲里我們的身份是什么呢
#返回請求頭信息
print(r.request.headers)

這就相當(dāng)于告訴服務(wù)器,我是python的requests模塊,我來找你了,大多數(shù)網(wǎng)站看到“非正常瀏覽器“的身份都不會(huì)返回?cái)?shù)據(jù)給你的,所以我們要假裝成是正常瀏覽器
我們在剛剛的headers面板里可以看到自己的user-agent的信息,可以復(fù)制下來,requests請求的時(shí)候加上個(gè)參數(shù)headers,
headers = {'user-agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'}
url = 'https://bbs.hupu.com/rockets-10'
r = requests.get(url,headers=headers)
但是復(fù)制粘貼還是略麻煩,我們可以用fake_useragent來偽造個(gè)user-agent
from fake_useragent import UserAgent
ua = UserAgent()
print(ua.random)

當(dāng)然,這樣我們還是無法得到第十一頁的標(biāo)題,因?yàn)槲覀冊跒g覽器上輸入第十一頁的網(wǎng)址的時(shí)候,就彈出登錄界面,用瀏覽器訪問就相當(dāng)于是正常瀏覽器身份了,所以不是user-agent的問題
難道每次我們都要手動(dòng)登錄才能爬嗎?有沒有方便點(diǎn)的方法?這種情況我們就要用上cookie了
首先我們要老老實(shí)實(shí)地手動(dòng)登陸一次,這是避免不了的,然后打開https://bbs.hupu.com/rockets-10,右鍵檢查,找到rockets-10的請求頭信息,找到cookie,添加到headers里

ua = UserAgent()
headers = {'user-agent':ua.random,
'cookie':'把你的cookie粘貼過來'}
url = f'https://bbs.hupu.com/rockets-{n}'
r = requests.get(url,headers=headers)
添加了cookie,嘗試一下
for i in range(101):
get_title(i)
