Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
們已經學到很多反爬機制以及相應的反反爬策略。使用那些手段,其實已經完全可以完成絕大多數的爬蟲任務。但是,還是有極個別的情況下,會出現諸如 JS 加密和 JS 混淆之類的高深反爬機制。
如果不幸遇到這種反爬機制,一個明智之舉是給站長點個贊,然后恭恭敬敬選擇放棄,去別的地方找數據。
當然,還是那句話,我們可以選擇不爬,但是對付 JS 加密和 JS 混淆的方法卻不可以不會。
這里就以中國空氣質量在線檢測平臺為例,介紹 JS 加密和 JS 混淆的實現和破解方法。
要爬取的網站:https://www.aqistudy.cn/html/city_detail.html
這個網站正在升級,所以頁面無法正常顯示。這也意味著這個網站本身的 JS 解密是有問題的(如果沒問題就能顯示了),所以最后我們并不能完全解析出數據來。雖然如此,這個網站仍然是學習 JS 加密和 JS 混淆的相當不錯的平臺。
閑話少說,開始干活!
首先瀏覽器打開網頁,并打開調試臺的抓包工具。修改查詢條件(城市的名稱 + 時間范圍),然后點擊查詢按鈕,捕獲點擊按鈕后發起請求對應的數據包。點擊查詢按鈕后,并沒有刷新頁面,顯然發起的是 ajax 請求。該請求就會將指定查詢條件對應的數據加載到當前頁面中(我們要爬取的數據就是該 ajax 請求請求到的數據)。
分析捕獲到的數據包
該數據包請求到的是密文數據,為何在前臺頁面顯示的卻是原文數據呢?
原來,在請求請求到密文數據后,前臺接受到密文數據后使用指定的解密操作(JS 函數)對密文數據進行了解密操作,然后將原文數據顯示在了前臺頁面。
接下來的工作流程:
首先先處理動態變化的請求參數,動態獲取該參數的話,就可以攜帶該參數進行請求發送,將請求到的密文數據捕獲到。
抽絲剝繭,首先從 getData 函數實現中找尋 ajax 請求對應的代碼。在該函數的實現中沒有找到 ajax 代碼,但是發現了另外兩個函數的調用,getAQIData() 和 getWeatherData()。ajax 代碼一定是存在于這兩個函數實現內部。
另外,這里記住一個參數,type==’HOUR‘,它的含義是查詢時間是以小時為單位。這個參數我們后來會用到。
接下來我們就去分析 getAQIData() 和 getWeatherData(),爭取能夠找到 ajax 代碼。
我們找到這兩個函數的定義位置,還是沒有找到 ajax 請求代碼。不過我們卻發現它們同時調用了另外一個函數,getServerData(method,param,func,0.5)。它的參數的值可以為:
下一步當然就要找 getServerData 函數了,看看那個函數里面有沒有我們一致想要的發送 ajax 請求的代碼。
我們嘗試著在頁面中搜索,卻找不到這個函數。很顯然,它是被封裝到其他 js 文件中了。這時,我們可以基于抓包工具做全局搜索。
好消息是,我們順利找到了 getServerData 函數!壞消息是,這貨長得一點也不像是函數。
這是因為,這段 JS 函數代碼被加密的。這種加密的方式,我們稱為 JS 混淆。
JS 混淆,也就是對核心的 JS 代碼進行加密。
JS 反混淆,則是對 JS 加密代碼進行解密。
接下來我們要做的,就是 JS 反混淆,讓這段我們看不懂的東西,顯現出廬山真面目。
我們用的方法十分簡單粗暴,也就是暴力破解。使用這個網站就可以實現對 JS 混淆的暴力破解:https://www.bm8.com.cn/jsConfusion/
將 getServerData 函數所在的那一整行代碼都復制過來,粘貼到這個網址的文本輸入框中,然后點擊 開始格式化 即可:
終于,我們看到了 getServerData 的代碼,并且在其中發現了發送 ajax 的請求:
function getServerData(method, object, callback, period) {
const key=hex_md5(method + JSON.stringify(object));
const data=getDataFromLocalStorage(key, period);
if (!data) {
var param=getParam(method, object);
$.ajax({
url: '../apinew/aqistudyapi.php',
data: {
d: param
},
type: "post",
success: function (data) {
data=decodeData(data);
obj=JSON.parse(data);
if (obj.success) {
if (period > 0) {
obj.result.time=new Date().getTime();
localStorageUtil.save(key, obj.result)
}
callback(obj.result)
} else {
console.log(obj.errcode, obj.errmsg)
}
}
})
} else {
callback(data)
}
}
從這段代碼中,我們不難得出下面這幾個信息:
但是我們并不打算這么做。因為再繼續深挖下去,難度將會陡然增加。此時我們已經很疲憊了,如果繼續下去恐怕要瘋掉。而且,JavaScript 和 Python 畢竟是兩種語言,它們之間的方法和各種包都不相同。JavaScript 能實現的,Python 未必能夠輕松完成。所以重新寫一個加密和解密的腳本,并不是明智之舉。
更好的解決方案是,我們提供請求的明文數據,通過網站自己的 JS 代碼進行加密,得到加密的請求參數。使用這個參數,我們發送請求給服務端。拿到加密的響應數據后,再通過網站的 JS 代碼進行解密。
也就是說,我們接下來需要做的就是要調用兩個 JS 函數 decodeData 和 getParam,并拿到返回結果即可。
現在的問題是,在 Python 程序中如何調用 JS 函數呢?
這就涉及到一個新的概念:JS 逆向。JS 逆向,也就是在 Python 中調用 JS 函數代碼。
能夠實現 JS 逆向的方式有兩種:
pip install PyExecJS
接下來,我們就可以生成加密的請求數據了。
首先,把我們解析出來的那串代碼保存到本地,比如名為 code.js 的文件中。在里面我們補充一個函數,比如名字叫 getPostParamCode,用來發起我們的數據請求。之所以這樣做是因為使用 PyExecJS 調用 JS 函數時,傳入的參數只能是字符串。而 getParam 方法的參數需要用到 JS 的自定義對象。
我們只需在 code.js 中加上下面的代碼即可:
function getPostParamCode(method, type, city, start_time, end_time) {
var param={};
param.type=type;
param.city=city;
param.start_time=start_time;
param.end_time=end_time;
return getParam(method, param)
}
然后,使用 PyExecJS 調用里面的 getParam 方法,將我們的請求數據加密:
# 模擬執行decodeData的js函數對加密響應數據進行解密
import execjs
import requests
node=execjs.get()
# 請求參數
method='GETCITYWEATHER'
type='HOUR'
city='北京'
start_time='2020-03-20 00:00:00'
end_time='2020-03-25 00:00:00'
# 編譯js代碼
file='code.js' # js代碼的路徑
ctx=node.compile(open(file, encoding='utf-8').read())
# 將請求數據加密
encode_js=f'getPostParamCode("{method}", "{type}", "{city}", "{start_time}", "{end_time}")'
params=ctx.eval(encode_js)
# 使用加密的參數,發起post請求
url='https://www.aqistudy.cn/apinew/aqistudyapi.php'
response_text=requests.post(url, data={'d': params}).text
# 將響應數據解密
decode_js=f'decodeData("{response_text}")'
decrypted_data=ctx.eval(decode_js) # 如果順利,返回的將是解密后的原文數據
print(decrypted_data) # 執行會報錯:目前頁面中沒有數據。解密函數只是針對頁面中原始的數據進行解密。
自此,我們完成了 JS 加密和 JS 混淆的處理。這里我們總結一下這幾個概念:
附,ajax 請求的各個數據的含義:
頭條號每天堅持更新原創干貨技術文章,歡迎關注本頭條號"Linux學習教程",公眾號名稱“Linux入門學習教程"。
如需學習視頻,請復制以下信息到手機瀏覽器或電腦瀏覽器上:
zcwyou.com
隨著互聯網活動的增加,網絡攻擊變得更加復雜和具有挑戰性。
毫無疑問,互聯網用戶的數據安全意識已經成倍增長。如果你是站長或博主,保護用戶的敏感數據和隱私不受網絡罪犯的惡意侵害就成了你的首要責任。
在這里,SSL證書在加強您的網站安全性方面起著最有效和最關鍵的作用。因此,我們很有必要了解SSL的基本知識。
https vs http
安全套接字層(SSL)是一種安全協議,為互聯網上傳輸的數據提供加密,并對網絡服務器進行身份驗證。當您提交任何敏感資料時,SSL會對您的數據進行加密,以確保數據得到充分保護和安全,并且只有預期的收件人才能理解。SSL證書是由證書頒發機構頒發給網站的數字證書,它保證了用戶的web瀏覽器和web服務器之間的所有信息都是加密的。因此,SSL證書保護您的數據免受惡意竊取或破壞,如竊聽、中間人攻擊等。
一旦安裝SSL證書,網站協議將從HTTP轉移到安全的HTTPS。此外,您可以從瀏覽器中看到一個信任的標識,一個掛鎖,被添加到您的網站的URL中。如上圖所示。這可以確保訪問者使用安全連接進行通信。SSL證書增加了網站的用戶體驗,并幫助提高其在谷歌搜索引擎結果頁面上的排名。它還可以驗證網站的真實性。但是你怎么檢查這些信息呢?
ssl原理
在瀏覽器上單擊地址欄中網址前面的掛鎖,可以查看SSL證書信息。它還包含關于網站身份的信息。以下是SSL證書的主要內容:
web瀏覽器與web服務器通信,并使用該數據文件驗證網站的身份和SSL證書的狀態。
SSL證書安裝在您的web服務器上后,它會以公鑰和私鑰的形式提供一個可識別的數字識別號碼,用于服務器的認證。這些只不過是一長串任意生成的數字。它還允許服務器對用戶和服務器之間交換的敏感信息進行加密和解密。
根據您的安全需求和預算,您可以從Internet上許多著名的證書頒發機構(CAs)提供的各種選擇中購買SSL證書。因此,即使你預算有限,也要確保你的網站安全,使用有效的、便宜的SSL證書來贏得用戶的信任,保護你的品牌聲譽。因此,我們要了解不同類型的SSL證書。
SSL證書如何保護用戶數據和隱私
各種驗證級別
SSL證書的類型
證書頒發機構(CA)根據不同的驗證級別需求頒發各種SSL證書。因此,SSL證書可以根據這些不同的驗證級別過程進行分類,如下所示:
DV SSL證書申請驗證過程非常簡單,幾乎不需要復雜的申請流程。因此,在SSL市場上可用的所有其他類型中,它需要的時間最短。DV SSL證書廣泛用于非金融交易或用戶敏感數據的小型網站。它們是最便宜的選擇,也被廣泛用于保護博客。它只能提供基礎的認證。
CA需要進行域驗證,并驗證所有者公司的真實性后,才能簽發OV SSL證書。OV SSL證書顯示公司的身份信息,可通過訪問證書上的信息查看。該公司的詳細信息顯示在“主題標簽”中,如下面的例子所示:
ssl certification
中型企業比較普遍使用OV SSL證書,其發放時間比DV SSL證書長。由于證書所有者公司的驗證過程需要更多的時間,所以比DV SSL證書的費用更高。
EV SSL證書與最高程度的信任和聲譽相關聯。EV SSL證書必須經過以下嚴格的認證后才能簽發給組織。非常嚴格和徹底的背景驗證是簽發驗證程序的一部分,如其地址、目前的運營狀況、法律狀況等。因此,與DV和OV SSL證書相比,此SSL證書的簽發時間最長,成本也更高。該公司的名稱顯示在地址欄,因此可以驗證。可信任的標識,網站Logo在網站上的展示增加了用戶的信任。EV SSL證書廣泛應用于銀行、大型金融機構、電子商務網站等。
使用不同類型SSL證書確保網站安全的另一個決定因素是需要保護的域和子域的數量,它們如下:
單域SSL證書為您的網站保護一個域或子域。
為了保護多個域和子域,購買具有成本效益的通配符SSL證書或多域證書提供了更好的投資回報和更好的時間管理,而不是為每個域和子域使用單一域證書。
通配符SSL證書僅使用一個證書就可以保護主域和其所有子域。通配符證書使用格式為*.example.com的通用名稱,因此它將使用單個證書來保護多個子域。如果考慮到預算問題,建議購買并安裝通配符SSL證書,該證書將提供與較昂貴證書相同的加密級別。
多域或SAN(主題可選名稱)SSL證書僅使用一個證書就可以保護多個域/子域。您可以添加和修改Subject Alternative Name字段,并輕松保護不同域和子域之間的多個名稱。例如,只需一個多域(SAN) SSL證書。
統一通信證書(UCC)只不過是多域SSL證書,早期設計用于保護Microsoft Exchange和Live Communications服務器。現在,它被用來保護任何多個域名,只需一個證書。UCC證書具有組織驗證功能,還可以用作EV SSL證書,為用戶提供最高程度的信任和安全性。
總之,我們可以說,要贏得用戶的信任,并將你的在線業務或博客帶到新的成功高度,關注用戶數據的安全是至關重要的。要做到這一點,沒有比使用廉價的SSL來保護你的網站或博客更好的方法了,它可以幫助你瞄準你的安全需求和預算。
如果喜歡本文,歡迎轉發。本文已同步至博客站,尊重原創,轉載時請在正文中附帶以下鏈接:https://www.linuxrumen.com/rmxx/1994.html
在這里我就不再一一介紹每個步驟的具體操作了,因為在爬取老版數據的時候都已經講得非常清楚了,所以在這里我只會在重點上講述這個是這么實現的,如果想要看具體步驟請先去看我的文章內容,里面有非常詳細的介紹以及是怎么找到加密js代碼和api接口。
私信小編01即可獲取大量Python學習資料
58同城的數據爬取非常簡單,唯一有點難的就是字體的加密,除此之外其他的數據用xpath即可獲取。
想爬取不同地方的直接訪問鏈接即可:
數據在鏈接中,直接請求獲取即可。
既然是字體加密那么就先把字體尋找出來,尋找簡單,在開發者工具中的分類找到Font,然后搜索這個鏈接進行查找。
已經找到這個字體了,他是在請求頁面的時候返回的,然后他還是個base64的,只需要轉換一下再保存就可以了。
import requests
from lxml import etree
def get_data():
url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
headers={
'authority': 'bj.58.com',
'method': 'GET',
'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
'scheme': 'https',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'max-age=0',
'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
}
response=requests.get(url=url, headers=headers)
return response.text
def get_font(data):
html=etree.HTML(data)
script=html.xpath('//script[2]/text()')[0]
ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
with open('fangchan-secret.ttf','wb') as f:
f.write(base64.b64decode(ttf))
if __name__=='__main__':
data=get_data()
get_font(data)
import base64
from fontTools.ttLib import TTFont
import re
import requests
from lxml import etree
def get_data():
url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
headers={
'authority': 'bj.58.com',
'method': 'GET',
'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
'scheme': 'https',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'max-age=0',
'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
}
response=requests.get(url=url, headers=headers)
return response.text
def get_font(data):
html=etree.HTML(data)
script=html.xpath('//script[2]/text()')[0]
ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
with open('fangchan-secret.ttf','wb') as f:
f.write(base64.b64decode(ttf))
def parse_font():
font=TTFont('fangchan-secret.ttf')
bestcmap=font['cmap'].getBestCmap()
newmap=dict()
for key in bestcmap.keys():
value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
key=hex(key)
newmap[key]=value
print(newmap)
if __name__=='__main__':
data=get_data()
get_font(data)
parse_font()
我們發現字體編號和之前的不符合,比如:0x9476=7,而這里的是2,這是什么原因呢?是因為他的字體是動態生成的,每次返回的數字編號對應的值都是不同的,但是不影響我們代碼的正常運行與結果。
import base64
from fontTools.ttLib import TTFont
import re
import requests
from lxml import etree
def get_data():
url="https://bj.58.com/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1"
headers={
'authority': 'bj.58.com',
'method': 'GET',
'path': '/chuzu/?PGTID=0d200001-0000-11e9-58e6-a658f219b27c&ClickID=1',
'scheme': 'https',
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9',
'accept-encoding': 'gzip, deflate, br',
'accept-language': 'zh-CN,zh;q=0.9',
'cache-control': 'max-age=0',
'cookie': 'f=n; commontopbar_new_city_info=1%7C%E5%8C%97%E4%BA%AC%7Cbj; commontopbar_ipcity=bj%7C%E5%8C%97%E4%BA%AC%7C0; userid360_xml=C0E9739B2022549506AFBC01231A1DAA; time_create=1606640420140; xxzl_cid=f4a781439d9247f393d0a1629bec00df; xzuid=e0e5ea78-ac5a-491b-819d-a869ab37a7a7; xxzl_deviceid=2G3xFS3qwOviMHxtC%2FVEituhpmiI%2FJ%2BAmJ08cPBulZSe7LcSgT98WgFcyNDbzMXJ; id58=c5/nfF+bz1xVS0tAA7tjAg==; 58tj_uuid=116f1ed0-7c25-477e-8887-be3602fa2389; new_uv=1; utm_source=; spm=; init_refer=https%253A%252F%252Fbj.58.com%252Fchuzu%252Fsub%252Fpn70%252F%253Fpagetype%253Dditie%2526PGTID%253D0d3090a7-0000-1b87-3e2e-c6efe8d19973%2526ClickID%253D2; wmda_uuid=13712f08f0e555f110b1b2684ce9d709; wmda_new_uuid=1; wmda_session_id_11187958619315=1604046685879-d3ad7e5f-77f6-29d7; wmda_visited_projects=%3B11187958619315; als=0; f=n; new_session=0',
'sec-fetch-dest': 'document',
'sec-fetch-mode': 'navigate',
'sec-fetch-site': 'none',
'sec-fetch-user': '?1',
'upgrade-insecure-requests': '1',
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.75 Safari/537.36'
}
response=requests.get(url=url, headers=headers)
return response.text
def get_font(data):
html=etree.HTML(data)
script=html.xpath('//script[2]/text()')[0]
ttf=re.findall(".*?src:url\(\'data:application/font-ttf;charset=utf-8;base64,(.*?)\'\).*?",script,re.S)[0]
with open('fangchan-secret.ttf','wb') as f:
f.write(base64.b64decode(ttf))
def parse_font():
font=TTFont('fangchan-secret.ttf')
bestcmap=font['cmap'].getBestCmap()
newmap=dict()
for key in bestcmap.keys():
value=int(re.search(r'(\d+)', bestcmap[key]).group(1)) - 1
key=hex(key)
newmap[key]=value
return newmap
def parse_data(data,newmap):
for key,value in newmap.items():
key_=key.replace('0x','') + ';'
if key_ in data:
data=data.replace(key_,str(value))
html=etree.HTML(data)
house_list=html.xpath('//ul[@class="house-list"]/li')[:-1]
for house in house_list:
room=house.xpath('.//p[@class="room"]/text()')[0]
money=house.xpath('.//b[@class="strongbox"]/text()')[0]
print(room,money)
if __name__=='__main__':
data=get_data()
get_font(data)
newmap=parse_font()
parse_data(data,newmap)
*請認真填寫需求信息,我們會在24小時內與您取得聯系。