今天我們說說動態(tài)頁面的抓取最蕾,動態(tài)頁面的概念不是說網(wǎng)頁上的內(nèi)容是活動的,而是刷新的內(nèi)容由Ajax加載胚泌,頁面的URL沒有變化,具體概念問度娘肃弟。
就以男人都喜歡的美女街拍為例玷室,對象為今日頭條。
chrome打開今日頭條 ->搜索
https://www.toutiao.com/search/?keyword=街拍
開發(fā)者工具->network選項卡
圖2-1
很多條目笤受,各種請求穷缤,但Ajax其實有其特殊的請求類型,它叫作xhr箩兽。在圖6-3中津肛,我們可以發(fā)現(xiàn)一個名稱以getIndex開頭的請求,其Type為xhr汗贫,這就是一個Ajax請求身坐。用鼠標(biāo)點擊這個請求,可以查看這個請求的詳細(xì)信息落包。
選中這個xhr請求后部蛇,我們可以看到Request Headers中X-Requested-With:XMLHttpRequest,這就標(biāo)記了此請求是Ajax請求妥色。
點擊一下Preview乔妈,即可看到響應(yīng)的內(nèi)容绪氛,它是JSON格式的。這里Chrome為我們自動做了解析,點擊箭頭即可展開和收起相應(yīng)內(nèi)容玛臂,初步分析這里返回的是頁面上顯示出來的前二十條信息贴膘。
切換回第一個請求偶器,我們發(fā)現(xiàn)Response中的信息是這樣的
圖2-4
這就是原始鏈接 https://www.toutiao.com/search/?keyword=街拍 所返回的內(nèi)容捏题,只有六十多行代碼,執(zhí)行了一些JavaScript许师,所以我們最終看到的頁面不是由初始頁面返回的房蝉,而是后來執(zhí)行的JavaScript向服務(wù)器發(fā)送了Ajax請求僚匆,收到返回的真實數(shù)據(jù)后才顯示出來的。這就是動態(tài)頁面渲染的流程搭幻。
明白了整個流程后咧擂,我們要做的最重要的事就是分析返回數(shù)據(jù)的內(nèi)容,用python模擬Ajax請求檀蹋,拿到我們所希望抓取的數(shù)據(jù)松申。
def get_page(offset):
params = {
'offset': offset,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': '20',
'cur_tab': '1',
'from': 'search_tab',
}
url = 'https://www.toutiao.com/search_content/?'
try:
response = requests.get(url, params=params)
if response.status_code == 200:
return response.json()
except requests.ConnectionError:
return None
下滑幾次后,發(fā)現(xiàn)只有offset參數(shù)變化俯逾,所以贸桶,構(gòu)造url,requests獲得數(shù)據(jù)
這里拿到的數(shù)據(jù)是json格式的
def download_image(jsonData):
if jsonData.get('data'):
for item in jsonData.get('data'):
if item and 'article_url' in item.keys():
title = item.get('title')
article_url = item.get('article_url')
result = get_real_image_path(article_url)
if result: save_to_mongo(result)
'''
另外一種數(shù)據(jù)格式cell桌肴,cell type太多皇筛,主要分析上面一種
else:
#original_page_url
data = item.get('display')
#print(display)
#data = json.loads(display)
#print(data)
if data and 'results' in data.keys():
results = data.get('results')
original_page_urls = [item.get('original_page_url') for item in results]
# .get('results').get('original_page_url')
#title = item.get('display').get('title')
#print(title)
#print(original_page_urls)'''
'''
取出數(shù)據(jù)中的data段,發(fā)現(xiàn)只有前四張圖片的地址可以取到坠七,剩下的圖片必須進(jìn)入文章頁才能獲得水醋,我們?nèi)〕鑫恼马摰膗rl,requests獲得文章頁數(shù)據(jù)
def get_real_image_path(article_url):
headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
response = requests.get(article_url, headers=headers)
soup = BeautifulSoup(response.text, "lxml")
title = soup.select('title')[0].get_text()
image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
result = re.search(image_pattern, response.text)
if result:
result = result.group(1).replace('\\', '')
data = json.loads(result)
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
images_urls = [item.get('url') for item in sub_images]
for image_url in images_urls: download_real_image(image_url)
return {
'title': title,
'url' : article_url,
'image_urls': images_urls
}
這里需要加入UA頭彪置,否則返回不了數(shù)據(jù)离例,拿到數(shù)據(jù)后,發(fā)現(xiàn)圖片地址位于
這里用正則表達(dá)式
gallery: JSON.parse("(.*?)"),
匹配符合條件的悉稠,gallery: JSON.parse("")中的數(shù)據(jù)()這里在正則中表達(dá)的是轉(zhuǎn)義字符,有興趣的可以學(xué)習(xí)一下正則表達(dá)式艘包,這里就不贅述了
我們從sub_images中拿到了所有圖片地址的猛,下載過程就很簡單了
requests圖片地址,獲得的response中的content就是圖片的數(shù)據(jù)
def download_real_image(url):
print('downloading---', url)
try:
response = requests.get(url)
if response.status_code == 200:
save_image(response.content)
return None
except RequestException:
print('request image fail---', url)
return None
def save_image(content):
files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
if not os.path.exists(files_path):
os.mkdir(files_path)
file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
我們還可以把圖片的標(biāo)題和地址寫入數(shù)據(jù)庫
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print('save success', result)
return True
return False
完整代碼:jrtt.py
import requests
import re
import json
from hashlib import md5
import os
from bs4 import BeautifulSoup
import pymongo
from config import *
import time
client = pymongo.MongoClient(MONGO_URL, connect=False)
db = client[MONGO_DB]
def get_page(offset):
params = {
'offset': offset,
'format': 'json',
'keyword': '街拍',
'autoload': 'true',
'count': '20',
'cur_tab': '1',
'from': 'search_tab',
}
url = 'https://www.toutiao.com/search_content/?'
try:
response = requests.get(url, params=params)
if response.status_code == 200:
return response.json()
except requests.ConnectionError:
return None
def save_to_mongo(result):
if db[MONGO_TABLE].insert(result):
print('save success', result)
return True
return False
def download_real_image(url):
print('downloading---', url)
try:
response = requests.get(url)
if response.status_code == 200:
save_image(response.content)
return None
except RequestException:
print('request image fail---', url)
return None
def save_image(content):
files_path = '{0}/{1}'.format(os.getcwd(), 'tupian')
if not os.path.exists(files_path):
os.mkdir(files_path)
file_path = '{0}/{1}.{2}'.format(files_path, md5(content).hexdigest(), 'jpg')
if not os.path.exists(file_path):
with open(file_path, 'wb') as f:
f.write(content)
def get_real_image_path(article_url):
headers = {'user-agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/67.0.3396.87 Safari/537.36'}
response = requests.get(article_url, headers=headers)
soup = BeautifulSoup(response.text, "lxml")
title = soup.select('title')[0].get_text()
image_pattern = re.compile('gallery: JSON.parse\("(.*?)"\),', re.S)
result = re.search(image_pattern, response.text)
if result:
result = result.group(1).replace('\\', '')
data = json.loads(result)
if data and 'sub_images' in data.keys():
sub_images = data.get('sub_images')
images_urls = [item.get('url') for item in sub_images]
for image_url in images_urls: download_real_image(image_url)
return {
'title': title,
'url' : article_url,
'image_urls': images_urls
}
def download_image(jsonData):
if jsonData.get('data'):
for item in jsonData.get('data'):
if item and 'article_url' in item.keys():
title = item.get('title')
article_url = item.get('article_url')
result = get_real_image_path(article_url)
if result: save_to_mongo(result)
'''
另外一種數(shù)據(jù)格式cell想虎,cell type太多卦尊,主要分析上面一種
else:
#original_page_url
data = item.get('display')
#print(display)
#data = json.loads(display)
#print(data)
if data and 'results' in data.keys():
results = data.get('results')
original_page_urls = [item.get('original_page_url') for item in results]
# .get('results').get('original_page_url')
#title = item.get('display').get('title')
#print(title)
#print(original_page_urls)'''
def main():
STARTPAGE = 1
ENDPAGE = 2
for i in range(STARTPAGE, ENDPAGE):
time.sleep(1)
offset = i * 20
jsonData = get_page(offset)
download_image(jsonData)
if __name__ == "__main__":
main()
config.py
MONGO_URL = 'localhost'
MONGO_DB = 'jiepai'
MONGO_TABLE = 'jiepai'
GROUP_START = 0
GROUP_END = 20
KEYWORD = '街拍'