如何使用Beautiful Soup从<script>中提取内容

9
我正在尝试从此处的脚本标签中提取campaign_hearts和postal_code(代码太长,无法发布完整代码)。
<script>
...    
"campaign_hearts":4817,"social_share_total":11242,"social_share_last_update":"2020-01-17T10:51:22-06:00","location":{"city":"Los Angeles, CA","country":"US","postal_code":"90012"},"is_partner":false,"partner":{},"is_team":true,"team":{"name":"Team STEVENS NATION","team_pic_url":"https://d2g8igdw686xgo.cloudfront.net
...

我可以通过以下代码确定我需要的脚本:
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd
from time import sleep
import requests 
import re
import json


page = requests.get("https://www.gofundme.com/f/eric-stevens-care-trust")

soup = BeautifulSoup(page.content, 'html.parser')

all_scripts = soup.find_all('script')
all_scripts[0]

不过,我不知道如何提取我想要的值。(我非常新手 Python)。 这个线程为类似的问题推荐了以下解决方案(根据我正在使用的 html 进行了编辑)。

data = json.loads(all_scripts[0].get_text()[27:])

然而,运行这个脚本会产生一个错误:JSONDecodeError: Expecting value: line 1 column 1 (char 0).

既然我已经确定了正确的脚本,我该怎么做来提取我需要的值呢?我也尝试过在这里列出的解决方案,但无法导入解析器(Parser)。

4个回答

4
你可以通过使用 json 模块解析 <script> 的内容,然后获取你需要的值。例如:
import re
import json
import requests

url = 'https://www.gofundme.com/f/eric-stevens-care-trust'

txt = requests.get(url).text

data = json.loads(re.findall(r'window\.initialState = ({.*?});', txt)[0])

# print( json.dumps(data, indent=4) )  # <-- uncomment this to see all data

print('Campaign Hearts =', data['feed']['campaign']['campaign_hearts'])
print('Postal Code     =', data['feed']['campaign']['location']['postal_code'])

输出:

Campaign Hearts = 4817
Postal Code     = 90012

2
从未知道这种加载JSON的方式。太棒了! :) - Amit Ghosh
re.findall()[0] 不就是 re.search() 吗? - AMC
@AMC 它可以写成 re.search(r'window\.initialState = ({.*?});', txt).group(1) 或者 re.search(..)[1]。这只是个人口味的问题。 - Andrej Kesely
这只是品味问题。但是,由于.findall()会获取每一个匹配项,无论它是否稍后被丢弃,因此有些不同。 - AMC

2
使用的库越多,代码就越低效!以下是更简单的解决方案-
#This imports the website content.

import requests
url = "https://www.gofundme.com/f/eric-stevens-care-trust"
a = requests.post(url)
a= (a.content)
print(a)

#These will show your data.

campaign_hearts = str(a,'utf-8').split('campaign_hearts":')[1]
campaign_hearts = campaign_hearts.split(',"social_share_total"')[0]
print(campaign_hearts)

postal_code = str(a,'utf-8').split('postal_code":"')[1]
postal_code = postal_code.split('"},"is_partner')[0]
print(postal_code)   

1
只是补充一下:我不会说代码变得不那么“高效”,因为通常在谈论效率时,暗示的是速度效率,而这里并非如此。但它会变得不那么直观,因为它增加了理解更多不同库的需求。 - Kewin Dousse
2
是的,我只是想说,当使用时间宝石就可以完成工作时,您不需要整个无限手套,哈哈 :) - Amit Ghosh
确实如此 :) - Kewin Dousse
我很好奇,你为什么要使用POST请求? - AMC
说到效率,如果 OP 想要整个 JSON 内容会发生什么? - AMC
我在冲动的时刻写了POST。没有什么重要的。答案只是针对确切的问题。如果OP想要整个JSON;这段代码将无法工作。然后,OP可能想要很多东西.. - Amit Ghosh

1
这个暂时还可以,我可能会尝试编写纯lxml版本或者至少改进元素搜索。
这个解决方案使用正则表达式仅获取JSON数据,不包括window.initialState =和分号。
import json
import re

import requests
from bs4 import BeautifulSoup

url_1 = "https://www.gofundme.com/f/eric-stevens-care-trust"

req = requests.get(url_1)

soup = BeautifulSoup(req.content, 'lxml')

script_tag = soup.find('script')

raw_json = re.fullmatch(r"window\.initialState = (.+);", script_tag.text).group(1)

json_content = json.loads(raw_json)

1
你的json.loads失败是因为最后的分号。如果使用正则表达式仅提取对象字符串(不包括最后的分号),它将起作用。
from bs4 import BeautifulSoup
import numpy as np
import pandas as pd
from time import sleep
import requests 
import re
import json



page = requests.get("https://www.gofundme.com/f/eric-stevens-care-trust")

soup = BeautifulSoup(page.content, 'html.parser')

all_scripts = soup.find_all('script')
txt = all_scripts[0].get_text()
data = json.loads(re.findall(r'window\.initialState = ({.*?});', txt)[0])

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接