之前已經(jīng)爬過(guò)今日頭條街拍的美圖,今天再次完善一下代碼吗铐,并詳解爬取過(guò)程及遇到的坑东亦。廢話(huà)不多說(shuō),抓緊上車(chē)?yán)病?/p>
分析頁(yè)面
分析索引頁(yè)
我們打開(kāi)今日頭條官網(wǎng)唬渗,在在搜索框輸入「街拍」
然后點(diǎn)擊確定典阵,跳轉(zhuǎn)到街拍的詳情頁(yè)。
這里可以看到上方有四個(gè)框镊逝,分別是 綜合壮啊、視頻、圖集撑蒜、用戶(hù)歹啼。
兩種方式
看到這里玄渗,就有兩種不同的抓取方式。
- 抓取綜合下方的圖集狸眼,這個(gè)方式雖然可以抓取到圖片藤树,但是抓到的圖片只有四張或一張,也就是看到的顯示在標(biāo)題下方的圖片拓萌。而且獲取的圖片還不是高清的岁钓,還要替換每張圖片的地址格式。
-
抓取圖集下的圖片微王,這種方式可以抓到所有一個(gè)標(biāo)題下的圖片甜紫,但是一頁(yè)顯示的圖片抓取不到。
我看網(wǎng)上大多是第一種方式骂远,這次為了練習(xí),我們選取第二種方式腰根。
我們點(diǎn)擊圖集激才,打開(kāi)開(kāi)發(fā)者工具「按F12」,不斷下拉頁(yè)面额嘿,頁(yè)面地址沒(méi)有變化瘸恼,內(nèi)容不斷加載出來(lái),這一看就是 Ajax 加載的頁(yè)面册养。
這里面也有一些坑
- 如果你點(diǎn)擊圖集东帅,打開(kāi)開(kāi)發(fā)者工具,刷新一下球拦,你會(huì)發(fā)現(xiàn)靠闭,你的頁(yè)面在綜合這一欄。
-
你會(huì)發(fā)現(xiàn)你找到的上圖的參數(shù)跟我的不一樣坎炼。
這里你可以刷新之后點(diǎn)擊圖集愧膀,然后向下拖動(dòng)幾個(gè),讓頁(yè)面多加載一些谣光。
其中「cur_tab:3」這個(gè)參數(shù)中的數(shù)字對(duì)應(yīng)圖集檩淋,這下你明白了吧。
到這里就好辦了萄金,我們點(diǎn)擊 Preview
可以看到 data 下方有 article_url 當(dāng)然還有 image_url 蟀悦,你點(diǎn)擊 image_url 你會(huì)發(fā)現(xiàn)只有四個(gè) url ,復(fù)制鏈接在瀏覽器上打開(kāi)你發(fā)現(xiàn) TMD 還不是大圖氧敢,還是縮略圖日戈,所以我們不用它,我們獲取 article_url 福稳,獲取之后再次請(qǐng)求不就完了嗎涎拉。雖說(shuō)麻煩瑞侮,但是我們思路清晰,頭腦發(fā)熱鼓拧,四肢簡(jiǎn)單半火。哎不對(duì),跑題了季俩。
分析詳情頁(yè)
這里我們來(lái)看看詳情頁(yè)的內(nèi)容
這里我們隨便點(diǎn)開(kāi)一個(gè)組圖的 url 來(lái)分析钮糖,我們可以看到返回的數(shù)據(jù)是一大堆 html ,這里有必要說(shuō)一下,我們?cè)讷@取頁(yè)面內(nèi)容的時(shí)候酌住,一般瀏覽器會(huì)返回給我們的是 response 里的內(nèi)容店归。但是我們大多數(shù)爬取數(shù)據(jù),用 xpath 酪我、BeautifulSoup 獲取數(shù)據(jù)消痛,看的是 Elements 里的內(nèi)容。這里一定要看看 response 里的內(nèi)容和 Element 里的是否相同都哭。
這里的圖片地址還真是不好找秩伞,具體怎么找呢,點(diǎn)開(kāi)圖片的地址欺矫,復(fù)制下鏈接纱新,在HTML里「Ctrl + F」就發(fā)現(xiàn)了。是在紅色框里面的穆趴。
上代碼
分析完了就開(kāi)始上代碼爬取
看看需要引入那些庫(kù)
import requests
import re,json,os
from urllib.parse import urlencode
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
#引入模塊config中所有變量
from config import *
import pymongo
from hashlib import md5
from multiprocessing import Pool
這里引用的庫(kù)有點(diǎn)多脸爱,所有本文的干貨也是滿(mǎn)滿(mǎn)滴。
獲取索引頁(yè)數(shù)據(jù)
def get_page_index(offset,keyword):
# 獲取頁(yè)面的HTML
data = {
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': '20',
'cur_tab': 3,
'from':'gallery'
}
try:
url = 'https://www.toutiao.com/search_content/?' + urlencode(data)
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('請(qǐng)求失敗')
return None
獲取索引頁(yè)的內(nèi)容未妹,這里的「offset」是頁(yè)面的規(guī)律簿废、「keyword」是關(guān)鍵字,我們這篇文章是街拍教寂。通過(guò)構(gòu)造參數(shù)捏鱼,拼接 url 。返回頁(yè)面的 text酪耕。
解析索引頁(yè)
def parse_page_index(html):
# 獲取所有詳情頁(yè)的url
data = json.loads(html) #頁(yè)面是json格式的导梆,裝換成字符串格式
# data.keys()返回所有鍵名
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
這個(gè)函數(shù)的主要作用就是提取所有的 article_url 。代碼里面已經(jīng)詳細(xì)的說(shuō)明了內(nèi)容迂烁。
解析詳情頁(yè)的url
def get_page_detial(url):
#請(qǐng)求詳情頁(yè)的url
try:
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 '
'(KHTML, like Gecko) Chrome/53.0.2785.104 Safari/537.36 Core/1.53.4882.400 QQBrowser/9.7.13039.400'}
response = requests.get(url,headers=headers)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('請(qǐng)求詳情頁(yè)失敗')
return None
這里不加 headers 是獲取不到數(shù)據(jù)的看尼。
解析詳情頁(yè)
def parse_page_detial(html):
#獲取詳情頁(yè)的標(biāo)題和圖片地址url
soup = BeautifulSoup(html, 'lxml')
title = soup.select('title')[0].get_text()
#利用正則提取圖片地址
pattern = re.compile('.*?gallery: JSON.parse\("(.*?)\"\)', re.S)
result = re.search(pattern,html)
if result:
data = json.loads(result.group(1).replace('\\', ''))
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
#提取圖片
images = [item.get('url') for item in sub_images]
#保存圖片到本地
for image in images:download_image(image)
return {'title':title,
'image':images}
這里面有些東西要說(shuō)一下了。
首先盟步,這里獲取的頁(yè)面內(nèi)容是 json 格式的藏斩,我們看一下這里的內(nèi)容
這里獲取用BeautifulSoup 獲取 title 很方便,直接去第一個(gè) title 就好了却盘,關(guān)鍵就在這個(gè)image的提取狰域。
這里是在紅色框里的媳拴,這里涉及到了正則的用法,代碼里用到了反斜杠兆览,這里是轉(zhuǎn)義匹配屈溉,要不然正則會(huì)匹配不到想要的數(shù)據(jù)。
還有在源代碼中出現(xiàn)了好多反斜杠抬探,不去除掉還是沒(méi)辦法匹配子巾。
這些坑跨過(guò)之后就一帆風(fēng)順了。
保存到MongoDB
'''配置文件'''
#鏈接地址
MONGO_URL = 'localhost'
#數(shù)據(jù)庫(kù)名稱(chēng)
MONGO_DB = 'jiepai'
#表名稱(chēng)
MONGO_TABLE = 'jiepai'
KEY_WORD = '街拍'
這是一些配置文件,注意小压,這里的配置文件是在另一個(gè)python文件中寫(xiě)的线梗,所以說(shuō)開(kāi)頭引入的庫(kù)中有一個(gè)注釋。
#引入模塊config中所有變量
from config import *
import pymongo
#聲明MongoDB對(duì)象
client = pymongo.MongoClient(MONGO_URL)
db = client[MONGO_DB]
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print('存儲(chǔ)到MongoDB成功')
這里插入到MongoDB怠益。
保存到本地
def save_image(result):
file_path = '{0}/{1}{2}'.format(os.getcwd(),md5(result).hexdigest(),'jpg')
if not os.path.exists(file_path):
with open(file_path,'wb') as f:
f.write(result)
這里用了 hashlib 庫(kù)的 md5 這個(gè)方法仪搔,目的是為了防止圖片的重復(fù),這個(gè)方法會(huì)根據(jù)圖片的內(nèi)容生成唯一的字符串蜻牢,用來(lái)去重最好不過(guò)了僻造。
這里說(shuō)保存圖片,沒(méi)有下載圖片孩饼,怎么保存,所以還要先下載圖片竹挡。
def download_image(url):
try:
print('正在下載',url)
r = requests.get(url)
if r.status_code == 200:
save_image(r.content)
return False
except RequestException:
print('請(qǐng)求圖片出錯(cuò)')
return False
細(xì)心的伙伴們已經(jīng)發(fā)現(xiàn)镀娶,我們?cè)诮馕鲈斍轫?yè)的時(shí)候插入的這個(gè)下載圖片的函數(shù)。
開(kāi)啟多線(xiàn)程抓取
def main(offset):
# 調(diào)用函數(shù)
html = get_page_index(offset,KEY_WORD)
for url in parse_page_index(html):
html = get_page_detial(url)
if html:
result = parse_page_detial(html)
save_to_mongo(result)
if __name__ == '__main__':
pool = Pool()
group = [x * 20 for x in range(1,21)]
pool.map(main,group)
pool.close()
main()
這里聲明一個(gè)線(xiàn)程池揪罕,調(diào)用 map 方法開(kāi)啟線(xiàn)程就可以了梯码。
總結(jié)
到這里整個(gè)抓取過(guò)程就結(jié)束了『脝總體下來(lái)代碼量要比平時(shí)抓取的要大轩娶,知識(shí)點(diǎn)也有很多。在這個(gè)過(guò)程中框往,即使找著代碼敲也會(huì)發(fā)現(xiàn)不少的問(wèn)題鳄抒。抓取的過(guò)程就是不斷調(diào)試的過(guò)程。
點(diǎn)個(gè)贊再走唄椰弊。