一、簡介
有時候我們在用 requests 抓取頁面的時候,得到的結果可能和在浏覽器中看到的不一樣:在浏覽器中可以看到正常顯示的頁面資料,但是使用 requests 得到的結果并沒有。這是因為 requests 擷取的都是原始的 HTML 文檔,而浏覽器中的頁面則是經過 JavaScript 處理資料後生成的結果,這些資料的來源有多種,可能是通過 Ajax 加載的,可能是包含在 HTML 文檔中的,也可能是經過 JavaScript 和特定算法計算後生成的。
對于第一種情況,資料加載是一種異步加載方式,原始的頁面最初不會包含某些資料,原始頁面加載完後,會再向伺服器請求某個接口擷取資料,然後資料才被處理進而呈現到網頁上,這其實就是發送了一個 Ajax 請求。
照 Web 發展的趨勢來看,這種形式的頁面越來越多。網頁的原始 HTML 文檔不會包含任何資料,資料都是通過 Ajax 統一加載後再呈現出來的,這樣在 Web 開發上可以做到前後端分離,而且降低伺服器直接渲染頁面帶來的壓力。
是以如果遇到這樣的頁面,直接利用 requests 等庫來抓取原始頁面,是無法擷取到有效資料的,這時需要分析網頁背景向接口發送的 Ajax 請求,如果可以用 requests 來模拟 Ajax 請求,那麼就可以成功抓取了。
1. 什麼是Ajax
Ajax,全稱為 Asynchronous JavaScript and XML,即異步的 JavaScript 和 XML。它不是一門程式設計語言,而是利用 JavaScript 在保證頁面不被重新整理、頁面連結不改變的情況下與伺服器交換資料并更新部分網頁的技術。
對于傳統的網頁,如果想更新其内容,那麼必須要重新整理整個頁面,但有了 Ajax,便可以在頁面不被全部重新整理的情況下更新其内容。在這個過程中,頁面實際上是在背景與伺服器進行了資料互動,擷取到資料之後,再利用 JavaScript 改變網頁,這樣網頁内容就會更新了。
2. 手寫Ajax接口服務
- 架構簡介
Flask是一個基于Python并且依賴于Jinja2模闆引擎和Werkzeug WSGI 服務的一個微型架構 WSGI :Web Server Gateway Interface(WEB服務網關接口),定義了使用python編寫的web app與web server之間接口格式
- 環境搭建
pip install flask
3. 前端程式設計
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<style>
td{
width: 65px;
text-align: center;
font-size: 18px;
}
</style>
<body>
<h1>hello</h1>
<!--
style 寫 CSS
HTML文法 标簽 table 表格
-->
<table style="margin: 0 auto; border: 1px solid; margin-top: 100px">
<tr>
<th>序号</th>
<th>姓名</th>
<th>年齡</th>
</tr>
<tbody>
{% for i in data.data %}
<tr>
<td>{{ i.id }}</td>
<td>{{ i.name }}</td>
<td>{{ i.age }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</body>
</html>
4.後端接口設計(Flask)
from flask import Flask,render_template,jsonify
# 執行個體
app = Flask(__name__)
@app.route('/')
def ff():
return render_template('feifei.html')
@app.route('/xl')
def susu():
data = []
for i in range(1, 10):
data.append(
{'id': i, 'name': '魚' + str(i), 'age': 18 + i}
)
# 構造資料格式
content = {
'status': 0,
'data': data
}
return jsonify(data=content)
@app.route('/api')
def index():
data = []
for i in range(1,7):
data.append(
{'id':i, 'name': '魚'+str(i), 'age':18+i}
)
# 構造資料格式
content = {
'status': 0,
'data':data
}
print(content)
# flask 架構 傳回資料給前端
return render_template('index.html',data=content)
if __name__ == '__main__':
app.run()
二、基本原理
初步了解了 Ajax 之後,我們再來詳細了解它的基本原理。發送 Ajax 請求到網頁更新的這個過程可以簡單分為以下 3 步:
- 發送請求
- 解析内容
- 渲染網頁
三、資料采集案例
1. Ajax 資料加載
Ajax 其實有其特殊的請求類型,其 Type 為 xhr,這就是一個 Ajax 請求。用滑鼠點選這個請求,可以檢視這個請求的詳細資訊。
示例網站:
https://danjuanapp.com/rank/performance
http://www.cninfo.com.cn/new/commonUrl?url=disclosure/list/notice#szseGem
https://cs.lianjia.com/
2. 資料采集實戰
采集位址:https://cs.lianjia.com/
# encoding: utf-8
import requests
from loguru import logger
import json
class Spdier_data():
def __init__(self):
self.headers = {
"Accept": "application/json, text/javascript, */*; q=0.01",
"Accept-Language": "zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7,zh-TW;q=0.6",
"Cache-Control": "no-cache",
"Connection": "keep-alive",
"Pragma": "no-cache",
"Referer": "https://cs.fang.lianjia.com/loupan/pg2/",
"Sec-Fetch-Dest": "empty",
"Sec-Fetch-Mode": "cors",
"Sec-Fetch-Site": "same-origin",
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.4951.67 Safari/537.36",
"X-Requested-With": "XMLHttpRequest",
"sec-ch-ua": "^\\^",
"sec-ch-ua-mobile": "?0",
"sec-ch-ua-platform": "^\\^Windows^^"
}
def http(self,url,params):
res = requests.get(url,params=params,headers=self.headers)
if res.status_code == 200:
return res
def get_data(self,url,params):
response = self.http(url=url,params=params)
items = response.json()
lists = items.get('data').get('list')
for i in lists:
item = {}
item['address'] = i.get('address')
item['city_name'] = i.get('city_name')
item['decoration'] = i.get('decoration')
item['district'] = i.get('district')
item['title'] = i.get('title')
item['show_price_info'] = i.get('show_price_info')
logger.info(json.dumps(item))
self.save_data(item)
def save_data(self,data):
with open('data.json','a',encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=4)
f.write(',')
def run(self):
for i in range(1,3):
url = "https://cs.fang.lianjia.com/loupan/pg{}/".format(str(i))
params = {
"_t": "1"
}
self.get_data(url,params)
if __name__ == '__main__':
Spdier_data().run()
四、資料去重算法
資料去重算法使用redis進行承載
redis 去重 集合類型,還有布隆算法。都是資料去重常用技術。
# encoding: utf-8
import requests
from base_request import Spiders
from lxml import etree
from loguru import logger
import time
import redis
client = redis.Redis()
import hashlib
class Crawl(Spiders):
def __init__(self):
self.url = 'https://36kr.com/information/web_news/latest/'
self.maps = lambda x:x[0] if x else x
def ma5_data(self,content):
m = hashlib.md5()
m.update(content.encode())
return m.hexdigest()
def crawl(self):
res = self.fetch(self.url)
html = etree.HTML(res.text)
obj = html.xpath('//div[@class="information-flow-list"]/div')
for i in obj:
title = self.maps(i.xpath('.//p[@class="title-wrapper ellipsis-2"]/a/text()'))
data_z = self.ma5_data(title) # 對資料進行壓縮 減少記憶體損耗
tag = client.sadd('36k',data_z) # 傳回值 0 1
if tag:
# 表示資料可以繼續爬 對資料進行入庫 logger = print
logger.info('可以入庫{}'.format(title))
else:
time.sleep(5)
logger.info('休息5秒鐘')
self.crawl()
def save(self,data):
with open('data.txt','a',encoding='utf-8') as x:
x.write(data)
x.write('\r\n')
def run(self):
while True:
logger.info('開始啟動爬蟲')
self.crawl()
if __name__ == '__main__':
Crawl().run()