一、目標網頁及要求
目標網頁:
https://www.xuexi.cn/f997e76a890b0e5a053c57b19f468436/018d244441062d8916dd472a4c6a0a0b.html
要求:
- 爬取頁面中的詳情頁文章標題、內容、發布時間、文章來源,存入本地mongodb數據庫
- 同時在本地創建一個文件夾,在該文件夾下以文章標題.txt創建文本,寫入文章內容
2、目標頁面分析
以Chrome瀏覽器為例,通過F12打開抓包工具,按F5刷新下頁面,ctr+F進行全局搜索頁面中的內容,發現該頁面內容是通過ajax動態獲取的
Method:GET
url:https://www.xuexi.cn/lgdata/f997e76a890b0e5a053c57b19f468436/018d244441062d8916dd472a4c6a0a0b.json?_st=27037952
將響應內容通過新窗口打開(安裝過JSONView插件),可以很直觀的查看Json數據結構
通過分析發現,詳情頁鏈接都在title下的link中,並且分為:帶請求參數id和不帶請求參數兩種形式
3、詳情頁分析
接下來分別對這兩種形式的詳情頁鏈接進行訪問,繼續按照目標主頁頁面的分析步驟,發現數據是通過js動態加載的
攜帶請求參數id:
詳情頁url :https://www.xuexi.cn/lgpage/detail/index.html?id=10942099116228124117&item_id=10942099116228124117
數據來源js:https://boot-source.xuexi.cn/data/app/10942099116228124117.js?callback=callback&_st=1622280445799
其它詳情頁數據來源js都是這種格式
規律如下:
通過詳情頁url提取id,然后_st參數是16開頭的13位數字,沒錯,就是時間戳*1000后的整數部分,這樣我們就能通過詳情頁url來找到其數據來源js的url
提取數據:
-
- 首先通過正則提取出dict數據,然后通過jsonpath規則提取出目標數據
- 標題:‘$..title’
- 內容:'$..content',之后通過xpath提取p或者span標簽中的內容
- 來源:'$..show_source'
- 出版時間:'$..publishi_time'
不帶請求參數:
詳情頁url :https://www.xuexi.cn/5c39c314138da31babf0b16af5a55da4/e43e220633a65f9b6d8b53712cba9caa.html
數據來源js:https://www.xuexi.cn/5c39c314138da31babf0b16af5a55da4/datae43e220633a65f9b6d8b53712cba9caa.js
其它詳情頁都是以e43e220633a65f9b6d8b53712cba9caa.html結尾,js頁都是以datae43e220633a65f9b6d8b53712cba9caa.js結尾
規律如下:
將詳情頁url中的e43e220633a65f9b6d8b53712cba9caa.html替換成datae43e220633a65f9b6d8b53712cba9caa.js即可
提取數據:
-
- 首先通過正則提取出dict數據,然后通過jsonpath規則提取出目標數據
- 標題:'$.fp8ttetzkclds001..frst_name'
- 內容:'$.fp8ttetzkclds001.detail.content',之后通過xpath提取p或者span標簽中的內容
- 來源:'$.fp8ttetzkclds001..source'
- 出版時間:'$.fp8ttetzkclds001..original_time'
4、代碼實現
# coding:utf-8
import jsonpath import json import re import requests import time import os import pymongo import fake_useragent from lxml import etree class XuexiSpider: def __init__(self): ua = fake_useragent.UserAgent().random # 生成隨機User-Agent
self.headers = { 'User-Agent':ua } self.json_data_url = 'https://www.xuexi.cn/lgdata/f997e76a890b0e5a053c57b19f468436/018d244441062d8916dd472a4c6a0a0b.json?_st=27035433' #首頁數據來源
self.mongoClient = pymongo.MongoClient('mongodb://localhost:27017') def work(self): '''開啟爬蟲'''
if not os.path.exists('./data'): os.mkdir('./data') # 發送請求,獲取首頁dict數據
dict_data = requests.get(self.json_data_url,headers=self.headers).json() # 提取詳情頁link
detail_page_links = jsonpath.jsonpath(dict_data,'$..title.link') for link in detail_page_links: if link.endswith('html'): self.parse_link_no_id(link) else: self.parse_link_with_id(link) self.mongoClient.close() #關閉數據庫連接
def parse_link_with_id(self,link): '''解析url中帶有id參數的鏈接''' id = re.findall(r'id=(\d+)',link)[0] #提取link中的id
_st = str(time.time()*1000).split('.')[0] #構造_st:當前時間戳*1000后取整數部分,然后轉成str
js_link = 'https://boot-source.xuexi.cn/data/app/{0}.js?callback=callback&_st={1}'.format(id,_st) response = requests.get(js_link,headers=self.headers).text response_dict_data = json.loads(re.findall(r'{.*}',response)[0]) title = jsonpath.jsonpath(response_dict_data,'$..title')[0] #詳情頁標題
raw_content = jsonpath.jsonpath(response_dict_data,'$..content')[0] #有的頁面只有圖片或者視頻,content內容為''或者<!--{img:0}-->\n\n,所以需要做個判斷
if not raw_content: content = ''
elif raw_content.startswith('<!--'): content = ''
else: tree = etree.HTML(raw_content) content_list = tree.xpath('//p/text()|//span/text()') #文本內容存放在p或者span標簽內
content = '\n\n'.join(content_list) #詳情頁內容
source = jsonpath.jsonpath(response_dict_data,'$..show_source')[0] #詳情頁文章來源
publish_time = jsonpath.jsonpath(response_dict_data,'$..publish_time')[0] #詳情頁文章出版時間
item = { 'title':title, 'content':content, 'source':source, 'publish_time':publish_time } self.pipeline(item) def parse_link_no_id(self,link): '''解析url中不帶有id參數的鏈接''' js_link = link.replace('e43e220633a65f9b6d8b53712cba9caa.html','datae43e220633a65f9b6d8b53712cba9caa.js') response = requests.get(js_link, headers=self.headers).text response_dict_data = json.loads(re.findall(r'{.*}', response)[0]) title = jsonpath.jsonpath(response_dict_data, '$.fp8ttetzkclds001..frst_name')[0] # 詳情頁標題
raw_content = jsonpath.jsonpath(response_dict_data, '$.fp8ttetzkclds001.detail.content')[0] #content內容可能為空字符串,所以需要做個判斷
if not raw_content: content = ''
elif raw_content.startswith('<!--'): content = ''
else: tree = etree.HTML(raw_content) content_list = tree.xpath('//p/text()|//span/text()') content = '\n\n'.join(content_list) # 詳情頁內容
source = jsonpath.jsonpath(response_dict_data, '$.fp8ttetzkclds001..source')[0] # 詳情頁文章來源
publish_time = jsonpath.jsonpath(response_dict_data, '$.fp8ttetzkclds001..original_time')[0] # 詳情頁文章出版時間
item = { 'title': title, 'content': content, 'source': source, 'publish_time': publish_time } self.pipeline(item) def pipeline(self,item): '''存儲數據'''
# 寫入本地文件,文件名:詳情頁標題.txt
with open('./data/'+item['title']+'.txt','w',encoding='utf-8') as f: f.write(item['content']) #存入mongodb數據庫
db = self.mongoClient.test #獲取數據庫
collection = db.xuexi #獲取集合
collection.insert_one(item) #集合中插入數據
if __name__ == '__main__': xuexi = XuexiSpider() xuexi.work()
由於爬取的數據不是很多,就沒有采用多線程或協程異步去實現代碼
爬取結果:
文本內容:
至此,大功告成!!!
分析案例來源:https://www.cnblogs.com/bobo-zhang/p/10561617.html#4216119