Python在HTML中查找JSON并获取选择值

时间:2018-01-18 20:24:49

标签: python json beautifulsoup

尔加。我花了相当多的时间试图找到如何做到这一点,无论是正确甚至是黑客,我只是难倒。我有2500多个HTML文件,我已经从网站上下载了,我只需要从任何给定页面中提取有限数量的信息:页面描述的谈话标题(所以我可以整理这些数据)我们已经拥有的一个巨大的CSV,然后是给出一个特定讲话的事件,以及发表讲话的日期。

这些网页的HTML非常庞大,并且填充了<script>个元素。我只想要一个q后面的那个。启动此块的行如下所示:

<script>q("talkPage.init", {

以下是相当多的数据。我只需要看起来像这样的三个项目:

"event":"TEDGlobal 2005",
"filmed":1120694400,
"published":1158019860,

幸运的是,"filmed""published"只在此大块中出现一次,但"event"多次出现。它始终是一样的,所以我不关心这些脚本中的哪一个抓取。

我的想法是使用 BeautifulSoup 来查找<script>q元素,然后将其传递到 json 模块进行解析,但我无法弄清楚告诉抓住<script>元素后跟aq - 类和id很容易。接下来......不是那么多。

要开始处理JSON部分,我已经创建了一个文本文件,其中只包含<script>q元素的内容,但我承认获得 json 加载这个模块的效果不是很好。

我实验的代码首先加载了我感兴趣的JSON块的文本文件,然后尝试对其进行解码,以便我可以用它做其他事情:

import json

text = open('dawkins_script_element.txt', 'r').read()
data = json.loads(text)

但显然JSON解码器并不像我所拥有的那样,因为它会抛出ValueError: Expecting value: line 1 column 1 (char 0)。呸!

这个脚本元素的前三行是什么样的:

<script>q("talkPage.init", {
"el": "[data-talk-page]",
"__INITIAL_DATA__":

这就是我现在所处的位置。任何可以在 json 上完成任务以完成此任务的灯光将非常感激。

3 个答案:

答案 0 :(得分:3)

在不了解完整背景的情况下,这是一个穷人的尝试:

假设您的html看起来像这样:

new Map(Object.entries(object))

您可以这样编码:

<script>foo</script>
<script>bar</script>
<script>q("talkPage.init",{
"foo1":"bar1",
"event":"TEDGlobal 2005",
"filmed":1120694400,
"published":1158019860,
"foo2":"bar2"
})</script>
<script>q("talkPage.init",{
"foo1":"bar1",
"event":"foobar",
"filmed":123,
"published":456,
"foo2":"bar2"
})</script>
<script>foo</script>
<script>bar</script>

然后你可以开始解释jsons:

res = requests.get(url) # your link here
soup = bs4.BeautifulSoup(res.content)
my_list = [i.string.lstrip('q("talkPage.init", ').rstrip(')') for i in soup.select('script') if i.string and i.string.startswith('q')]

# my_list should now be filled with all the json text that is from a <script> tag followed by a 'q'
# note that I lstrip and rstrip on the script based no your sample (assuming there's a closing bracket), but if the convention is different you'll need to update that accordingly.

#...#
my_jsons = []
for json_string in my_list:
    my_jsons.append(json.loads(json_string))

# parse your my_jsons however you want.

这里有很多假设。假设print(my_jsons[0]['event']) print(my_jsons[0]['filmed']) print(my_jsons[0]['published']) # Output: # TEDGlobal 2005 # 1120694400 # 1158019860 元素中的所有文字始终以<script>q开头,以q("talkPage.init",结尾。此外,它假设返回的文本遵循json格式进行下一阶段的解析。我还假设你知道如何解析json结果。

答案 1 :(得分:2)

您可以使用正则表达式匹配您想要的部分。

import re
# Filters the script-tag all the way to end ')' of q.
scipt_tag = re.findall(r'<script>q\((?s:.+)\)', t)
json_content = re.search(r'(?<=q\()(?s:.+)\)', script_tag[0]).group()
json_content = json_content[:-1]  # Strip last ')'

要找到你需要的东西,你可以使用pythons json库来解析它,或者将最后的东西与你想要的东西相匹配。由于filmedpublished是唯一的,event没有区别(据我所知?)

import json
json_content = json.loads(json_content)
json_content['event']  # or whatever

def get_val(a):
re.search('r(?<=' + a + r'\": )(.+)').group(0)

后者需要进行一些过滤,以删除尾随]""[之前的结尾,或者不想要的内容。

我听说beautifulsoup也是一个很好的匹配html东西的库,但我不太熟悉它。

答案 2 :(得分:0)

这是我最终使用的脚本,非常感谢@Idlehands和@Three。为了达到奇怪的单引号JSON,我获取了整个JSON元素并将其读入一个列表,用逗号分隔。它是一个黑客,但它主要起作用。

def get_metadata(the_file):

    # Load the modules we need
    from bs4 import BeautifulSoup
    import json
    import re
    from datetime import datetime

    # Read the file, load it into BS, then grab section we want
    text = the_file.read()
    soup = BeautifulSoup(text, "html5lib")
    my_list = [i.string.lstrip('q("talkPage.init", {\n\t"el": "[data-talk-page]",\n\t "__INITIAL_DATA__":')
               .rstrip('})')
               for i in soup.select('script') 
               if i.string and i.string.startswith('q')]

    # Read first layer of JSON and get out those elements we want
    pre_json = '{"' + "".join(my_list)
    my_json = json.loads(pre_json)
    slug = my_json['slug']
    vcount = my_json['viewed_count']
    event = my_json['event']

    # Read second layer of JSON and get out listed elements:
    properties = "filmed,published" # No spaces between terms!
    talks_listed = str(my_json['talks']).split(",")
    regex_list = [".*("+i+").*" for i in properties.split(",")]
    matches = []
    for e in regex_list:
        filtered = filter(re.compile(e).match, talks_listed)
        indexed = "".join(filtered).split(":")[1]
        matches.append(indexed)
    filmed = datetime.utcfromtimestamp(float(matches[0])).strftime('%Y-%m-%d')
    # published = datetime.utcfromtimestamp(float(matches[1])).strftime('%Y-%m-%d')
    return slug, vcount, event, filmed, #published