JavaScript中的JSON轉為Python可讀取
創作背景
最近本菜雞在爬一個網站的時候,意外發現所需 JSON
數據在頁面前端,但是不易提取,寫下此篇博客以記錄解決方案。
問題再現
數據是通過 JS
代碼傳遞的,大致格式(僅 作舉例說明 ,方便查看層次,實際 在同一行 )如下:
function (a, b, c, d){ return { title: a, data: [ { data: b }, { data: c }, { data: d } ] } }('title', 2, 3, 4)
我要提取的是整個 JSON
格式的數據。
如果要直接提取,可以使用
re.findall('return (\{.*?\})\}\(', content)
得到結果,但如果要解析數據,會報以下的錯:
意思是:鍵值對中的鍵需要被雙引號包含 。
所以我們要完成任務的話,需要解決以下問題:
- 鍵需要用
""
包含。 - 需要將形式參數
a, b, c, d
轉化為實際參數"title", 2, 3, 4
。
解決辦法
形參與實參的對應關系容易解決,所以先解決這個問題。
形參與實參對應關系
可以使用以下代碼得到 形式參數 。
''.join(re.findall('function\((.*?)\)\{', content)).split(',')
使用下述代碼獲得 實際參數 。
''.join(re.findall('}\((.*?)\)', content)).split(',')
因為 形式參數 和 實際參數 的 個數 一樣,所以可以根據 列表索引 建立對應關系,使用 np.c_
可以進行 列表橫向合並 。
代碼如下:
# 獲得實際參數Argument = ''.join(re.findall('}\((.*?)\)', content)).split(',')# 獲得形式參數Formal_parameter = ''.join(re.findall('function\((.*?)\)\{', content)).split(',')# 建立對應關系mapping = pd.DataFrame(np.c_[Formal_parameter, Argument])# 以形參作為索引mapping.set_index(0, drop=True, inplace=True)
結果如下:
格式化 JSON
要解決這個問題,我們需要先提取出 JSON
字符串,代碼如下:
string = ''.join(re.findall('return (\{.*?\})\}\(', content))
結果如下:
然後,我們需要有個思路,如下:
{
或,
後邊,:
之前的部分是 鍵 ,需要加上雙引號。:
、[
或{
後邊,]
、,
之前的部分都是 值 ,需要識別且替換。
給鍵加上雙引號
因為涉及到 插入元素 ,Python
中隻有 列表 能擔此重任,所以我們需要先 將字符串轉為列表 ,代碼如下:
string = list(''.join(re.findall('return (\{.*?\})\}\(', content)))
然後,我們需要設置一個變量,當識別出是 鍵 的時候就 置 1
,否則為 0
。
key_flag = 0
如果我們按照上文中的規則識別出 鍵 ,就要從當前位開始,一直到 :
之前的這部分都用 雙引號 包含。
還有些特殊情況,比如
- 嵌套字典 ,比如一個列表中值均為字典 。
- 空字典 ,
{
後邊是}
。
考慮到特殊情況,代碼如下:
index = 0 while True: # 如果索引超出范圍,就跳出循環 if index >= len(string): break # 給鍵加雙引號 if key_flag: # 在當前位插入一個雙引號 string.insert(index, '"') index += 1 # 循環讀取 while True: # 直到出現 ":",循環讀取的部分為 鍵 # 在 鍵 的最後添加一個雙引號 if string[index] == ':': string.insert(index, '"') # 重置 key_flag key_flag = 0 # 終止循環 break # 讀取下一位 index += 1 # 當前字符為 "{" 或 ",",則後邊的為 鍵 if string[index] in '{,': key_flag = 1 # 嵌套時,則將索引移向下一位 if string[index+1] in '{': index += 1 # 如果為空字典,則重置 key_value if string[index+1] in '}': key_flag = 0 index += 1
結果如下(因篇幅限制,代碼無法截全):
可以看到,已經將所有的鍵用雙引號包含。
識別且替換值
這一部分還是小有難度的。
首先,和上邊一樣,我們還是需要一個變量,記錄當前識別 值 的狀態,1
代表識別出來瞭,0
代表沒有。
value_flag = 0
不過也是有特殊情況:
- 值已經是字符串 ,但 字符串中有
:
。 - 值是
js
語句 ,不過其不是我們要提取的數據。
考慮到特殊情況,代碼如下:
while True: if index >= len(string): break # 檢測到 值 if value_flag: # 取出當前字符 value = string.pop(index) # 如果字符是數字、"[" 和 "{" 或者已經是字符串 if value in '"1234567890[{' or is_value: value_flag = 0 string.insert(index, value) index += is_value # 不符合上述情況 else: # 循環取出 值 字符串 while True: # 如果為 "," 或 "}",則代表已取完 if string[index] in ',}': break value += string.pop(index) # 如果 值 字符串在對應關系中,就替換 if value in mapping.index: # 因為是在當前位不斷插入,所以要將數據反向 trans = mapping.loc[value][::-1] # 如果不在,則直接替換成空字符串 else: trans = '""' # 計算索引要移動幾位 length = len(trans) # 插入對應的數據 for c in trans: string.insert(index, c) # 索引移動 index += length value_flag = 0 continue # 如果識別到 ":" 且該 ":" 不在值字符串中 if string[index] in ':' and not is_value: value_flag = 1 # 如果是值是字符串,則設置 is_value if string[index+1] in '"': is_value = 1 # 識別值字符串結束,並重置 is_value elif string[index] in '"' and is_value: is_value = 0 index += 1
結果如下:
可以看到,轉換的還是挺成功的。
總代碼
content = ''' function(a,b,c,d){return {title:a,data:[{data:b},{data:c},{data:d}]}}("title",2,3,4) ''' string = list(''.join(re.findall('return (\{.*?\})\}\(', content))) is_value = 0 key_flag = 0 value_flag = 0 index = 0 while True: if index >= len(string): break if key_flag: string.insert(index, '"') index += 1 while True: if string[index] == ':': string.insert(index, '"') key_flag = 0 break index += 1 elif value_flag: value = string.pop(index) if value in '"1234567890[{' or is_value: value_flag = 0 string.insert(index, value) index += is_value else: storage = index while True: if string[index] in ',}': break value += string.pop(index) if value in mapping.index: trans = mapping.loc[value][::-1] else: trans = '""' length = len(trans) for c in trans: string.insert(index, c) index += length value_flag = 0 continue if string[index] in '{,': key_flag = 1 if string[index+1] in '{': index += 1 if string[index+1] in '}': key_flag = 0 elif string[index] in ':' and not is_value: value_flag = 1 if string[index+1] in '"': is_value = 1 elif string[index] in '"' and is_value: is_value = 0 index += 1 ''.join(string)
結果如下:
這時候就可以使用 json.loads
來提取數據瞭。
結果如下:
不足
其實上述代碼是有不足的地方的。因為這段 js
代碼是特殊的,是 在一行 ,且 沒有多餘的空格 。
不過也是有解決辦法的:
如果 有空格 怎麼辦? —> 在 提取形參 和 使用形參轉換實參 時使用 strip
去除兩側的空格即可。
代碼不在一行 怎麼辦? —> 使用 .replace('\t', '').replace('\n', '')
去除 換行符 和 制表符 ,然後再進行格式化工作。
其實如果隻是 代碼不在一行 的問題的話,js.loads
會幫我們去除 \n
之類的,直接提取重要部分。
不過如果要使用我的代碼的話,目前隻支持 在一行 的 js
代碼。
還有就是有些 將鍵值對的值設置成邏輯運算式 ,比如 a || ""
這種,也不太好提取,還得根據問題調整。
這些都隻是思路,大傢可以自行嘗試,如果有問題也及時提出來。
到此這篇關於JavaScript中的JSON轉為Python可讀取的文章就介紹到這瞭,更多相關JavaScript JSON轉為Python內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!
推薦閱讀:
- Python實現快速保存微信公眾號文章中的圖片
- Python進階篇之正則表達式常用語法總結
- 用python實現學生信息管理系統
- 利用Python制作一個MOOC公開課下載器
- Python 如何保存json文件並格式化