運(yùn)行環(huán)境: python : 3.5.2 requests:2.11.1 pymongo:3.5.1 MongoDB:3.4.7
項(xiàng)目目的:爬取今日頭條中關(guān)于“街拍”圖集中的圖片恼布,并保存相關(guān)數(shù)據(jù)到數(shù)據(jù)庫
項(xiàng)目中需要用到的包
import re
from hashlib import md5
import pymongo
from bs4 import BeautifulSoup
from requests.exceptions import RequestException
import requests
from config import *
from multiprocessing import Pool
下面來一個(gè)一個(gè)解釋:
- json:本次爬取的數(shù)據(jù)多為 json 格式棚唆,所以需要將數(shù)據(jù)轉(zhuǎn)換為 json 再進(jìn)行下一步處理氯窍。
- md5:下載圖片給圖片命名時(shí)茫打,有可能有些圖片會(huì)重復(fù),為了避免重復(fù)下載淘正,使用 hashlib 模塊的 md5 方法根據(jù)圖片的內(nèi)容給圖片命名绣硝。(需要了解 hashlib 可點(diǎn)擊這里)
- pymongo:python 連接 mongodb 的包
- RequestException:在進(jìn)行網(wǎng)頁請(qǐng)求時(shí)矩动,可能會(huì)發(fā)生一些錯(cuò)誤,在這里直接拋出
- requests:請(qǐng)求庫
- config:代碼中的一些配置信息
- Pool:多線程提高代碼運(yùn)行效率
網(wǎng)站分析
- 網(wǎng)站圖集中不是采用翻頁爸吮,而是隨鼠標(biāo)的下滑自動(dòng)加載芬膝,其中只有請(qǐng)求參數(shù) “offset” 改變(0、20形娇、40遞增)
- 每個(gè)圖片集的 url 在 data 當(dāng)中锰霜,如圖
- 每張圖片的 url 在網(wǎng)頁文檔的 gallery 中,可以采用正則獲取 url
代碼詳情
一桐早、請(qǐng)求索引頁并解析
請(qǐng)求索引頁
def get_page_index(offset, keyword):
'''返回請(qǐng)求索引頁的代碼詳情'''
#請(qǐng)求參數(shù)設(shè)置
data = {
'offset': offset,
'format': 'json',
'keyword': keyword,
'autoload': 'true',
'count': 20,
'cur_tab': 3
}
url = 'http://www.toutiao.com/search_content/?' + urlencode(data)
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print("請(qǐng)求索引頁出錯(cuò)")
return None
解析索引頁
def parse_page_index(html):
'''解析索引頁癣缅,獲取頁面url'''
# 將 html 轉(zhuǎn)換為 json 格式的數(shù)據(jù)
data = json.loads(html)
if data and 'data' in data.keys():
for item in data.get('data'):
yield item.get('article_url')
二、請(qǐng)求詳情頁并解析
請(qǐng)求詳情頁
def get_page_detail(url):
'''獲取詳情頁的代碼'''
try:
response = requests.get(url)
if response.status_code == 200:
return response.text
return None
except RequestException:
print('請(qǐng)求詳情頁出錯(cuò)', url)
return None
解析頁面哄酝,獲取圖集名稱和每張圖片 url
def parse_page_detail(html, url):
'''解析詳情頁的代碼友存,獲取每張圖片的 url '''
soup = BeautifulSoup(html, 'lxml')
title = soup.title.text
# 或者 title = soup.select('title')[0].get_text()
# print(title)
images_pattern = re.compile('gallery:(.*?)\ssiblingList:', re.S)
result = re.search(images_pattern, html)
if result:
# print(result.group(1)[:-5])
# 把結(jié)果轉(zhuǎn)換為課處理的 json 格式
data = json.loads(result.group(1)[:-5])
if data and 'sub_images' in data.keys():
images = [item.get('url') for item in data.get('sub_images')]
for image in images:
download_imgae(image)
return {
'title': title,
'url': url,
'images': images
}
三、將數(shù)據(jù)保存到數(shù)據(jù)庫
數(shù)據(jù)庫配置信息
MONGO_URL = 'localhost' #數(shù)據(jù)庫地址
MONGO_DB = 'toutiao' #數(shù)據(jù)庫名稱
MONGO_TABLE = 'toutiao' #表名稱
連接數(shù)據(jù)庫
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
將數(shù)據(jù)保存到數(shù)據(jù)庫
def save_to_mongo(resutl):
'''把結(jié)果存儲(chǔ)到 mongodb 數(shù)據(jù)庫中'''
if db[MONGO_TABLE].insert(resutl):
print('存儲(chǔ)到MongoDB成功', resutl)
return True
return False
下載圖片并保存圖片
下載圖片
def download_imgae(url):
'''解析圖片url'''
print('正在下載:', url)
try:
response = requests.get(url)
if response.status_code == 200:
save_image(response.content)
return None
except RequestException:
print('請(qǐng)求圖片出錯(cuò)', url)
return None
保存圖片到當(dāng)前目錄
def save_image(content):
'''保存文件'''
file_path = '{0}\{1}.{2}'.format(os.getcwd(), md5(content).hexdigest(), 'jpg')
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
四陶衅、主函數(shù)
主函數(shù)
def main(offset):
'''主函數(shù)'''
html = get_page_index(offset, KEYWORD)
# print(html)
for url in parse_page_index(html):
html = get_page_detail(url)
if html:
result = parse_page_detail(html, url)
# print(result)
if result:
save_to_mongo(result)
程序入口
if __name__ == '__main__':
groups = [x * 20 for x in range(GROUP_START, GROUP_END)]
pool = Pool()
pool.map(main, groups)
參數(shù)說明
#需要爬取的頁數(shù)配置參數(shù)
GROUP_START = 1
GROUP_END = 20
#爬取關(guān)鍵詞
KEYWORD = '街拍'
巨坑之處
- 正則獲取
gallery
內(nèi)容時(shí)屡立,gallery
是以,
結(jié)束搀军,我當(dāng)時(shí)匹配時(shí)無法用逗號(hào)作為匹配結(jié)束膨俐,只能再加上下一行的siblingList:
勇皇,但是這樣的話就有空白符需要匹配,所以需要加上空白匹配符\s
焚刺。此時(shí)獲得的數(shù)據(jù)最后為逗號(hào)敛摘,還不能直接轉(zhuǎn)換為 json 格式的數(shù)據(jù)。這是本想著直接使用切片([:-1])即可去除逗號(hào),然而事情并不是如此的簡單檩坚,怎么都沒想多逗號(hào)后面竟然還有四個(gè)空格(此處請(qǐng)容許我說句MMP)∽帕茫現(xiàn)在在去分析,空白匹配符\s
沒有匹配到換行符匾委,難道siblingList:
前面還有空格M闲稹!赂乐! - 啟用多線程時(shí)薯鳍,連接數(shù)據(jù)庫會(huì)發(fā)生一個(gè)錯(cuò)誤,此時(shí)就需要在連接數(shù)據(jù)庫時(shí)添加參數(shù)
connect=False
- 在解析頁面時(shí)挨措,有些頁面不是我們需要的挖滤,無法解析到我們想要的結(jié)果。因此在執(zhí)行下一步時(shí)就需要判斷解析頁面的結(jié)果浅役。
結(jié)果展示
在短短的幾分鐘就下載了將近六百張圖片斩松,效率還是可以的
溫馨提示:啟動(dòng)程序前記得啟動(dòng)數(shù)據(jù)庫
完整代碼和輸出文件請(qǐng)?jiān)L問:[https://github.com/xieys/python_spyder/tree/master/jiepai) 歡迎Follow和star