BeautifulSoup4找不到所需的元素。问题是什么?

时间:2019-10-02 07:58:16

标签: python web-scraping beautifulsoup

我正在尝试编写一个程序,以提取文章的链接,这些文章的标题位于here

如果您检查源代码,则将看到该文章的每个链接都包含在元素h3中。例如

<h3 class="cd__headline" data-analytics="_list-hierarchical-xs_article_">
<a href="/2019/10/01/politics/deposition-delayed-impeachment-investigation/index.html">
<span class="cd__headline-text">State Department inspector general requests briefing on 
Ukraine with congressional staff</span><span class="cd__headline-icon cnn-icon"></span></a></h3>

我用python写了一个代码(我只显示程序的第一部分,因为这是出问题的地方)

import requests
import bs4
res = requests.get('https://edition.cnn.com/politics')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.text)
a0 = soup.select('h3[class="cd__headline"] > a')
a0

Output: []

出什么问题了?


我尝试了不同的模式

a0 = soup.select('a > span[class="cd__headline-text"]')

仍然没有运气

4 个答案:

答案 0 :(得分:1)

目标页面上的内容使用javascript动态加载。 初始服务器响应(res)根本没有您要查找的元素。检查res中的文本将确认这一点。

该问题最受好评的答案是here

简而言之,您需要使用某些东西来执行JavaScript,以加载所需的内容。

您的选择是Selenium(或任何无头浏览器工具),Scrapy,以及requests-HTML中建议的带有JS支持的中间件或派生产品this answer。或您可能找到的任何其他JS加载库。

答案 1 :(得分:1)

您有2个选择:

1)如其他人所述,首先使用Selenium或其他方法呈现页面,然后可以从呈现的html中提取内容。

2)查找嵌入在<script>标记中的数据,以我的经验,这可以帮助我大多数时候避免使用硒。困难的部分是找到它,然后将字符串处理为有效的json格式,以通过json.loads()进行读取。

我选择了选项2:

import requests
import bs4
import json
res = requests.get('https://edition.cnn.com/politics')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.text, 'html.parser')


tags = soup.find_all('script')
for tag in tags:
    if 'var CNN = CNN ||' in tag.text:
        jsonStr = tag.text
        jsonStr = jsonStr.split('siblings:')[-1].strip()
        jsonStr = jsonStr.split(']',1)[0] + ']}'
        jsonData = json.loads(jsonStr)

for article in jsonData['articleList']:
    headline = article['headline']
    link = 'https://edition.cnn.com' + article['uri']

    print ('Headline: %s\nLink: %s\n\n' %(headline, link))

输出:

Headline: Trump ratchets up anti-impeachment rhetoric as troubles mount
Link: https://edition.cnn.com/2019/10/02/politics/president-donald-trump-impeachment-democrats-pompeo/index.html


Headline: Here's what happened in another wild day of the Trump-Ukraine scandal
Link: https://edition.cnn.com/2019/10/01/politics/ukraine-guide-rudy-giuliani-trump-whistleblower/index.html


Headline: All the President's men: Trump's allies part of a tangled web 
Link: https://edition.cnn.com/2019/10/01/politics/trump-act-alone-ukraine-call/index.html


Headline: State Department inspector general requests briefing on Ukraine with congressional staff
Link: https://edition.cnn.com/2019/10/01/politics/deposition-delayed-impeachment-investigation/index.html


Headline: Senior GOP senator rebukes Trump, says whistleblower 'ought to be heard out'
Link: https://edition.cnn.com/2019/10/01/politics/grassley-whistleblower-statement/index.html


Headline: How Lindsey Graham's support for Trump — a man he once called a 'jackass' — has evolved
Link: https://edition.cnn.com/2019/10/01/politics/lindsey-graham-defends-trump-whistleblower/index.html


Headline: Federal judge blocks California law requiring Trump to release tax returns to appear on ballot
Link: https://edition.cnn.com/2019/10/01/politics/california-law-trump-tax-returns-blocked/index.html

...




我如何知道'var CNN = CNN ||'?

只需对html进行一点调查。我可以先查看源代码,然后find内的标题,然后找到它的标签。或者我通常要做的是制作一些临时脚本,这些脚本稍后会丢弃,以缩小搜索范围:

1)我在html中获得了每个标签

import requests
import bs4
import json
res = requests.get('https://edition.cnn.com/politics')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.text, 'html.parser')

# Get every tag in html
tags = soup.find_all()

2)浏览每个标签以查看标题是否在文本内。 标题经常更改,因此我只是转到浏览器上的URL,然后从主标题中选择一个子字符串。如果我现在转到https://edition.cnn.com/politics,标题之一将显示"Kurt Volker: Diplomat never 'fully on the Trump train' set to appear as first witness in Ukraine probe"。然后,我只是看看那里的子串是否存在。如果是这样,那么我可以进一步调查;如果没有,那么我很不走运,需要查看是否可以通过其他方式获取数据

for tag in tags:
    if "Kurt Volker: Diplomat never 'fully on the Trump train'" in tag.text:  
        tag_name = tag.name
        print ('Possibly found article in %s tag' %tag_name)

读出的内容:

Possibly found article in html tag
Possibly found article in head tag
Possibly found article in link tag
Possibly found article in link tag
Possibly found article in link tag
Possibly found article in link tag
Possibly found article in link tag
Possibly found article in link tag
Possibly found article in script tag

3)啊哈,它存在。了解html结构的工作原理后,html标签就是整个文档,然后每个顺序标签都是后代。我的经验告诉我,我很可能会在叶标记中找到叶节点/标记。现在,我将搜索脚本标签。

scripts = soup.find_all('script')
print (len(scripts))

4)我看到有28个<script>标签,所以我要看哪一个?

for idx, script in enumerate(scripts):
    if "Kurt Volker: Diplomat never 'fully on the Trump train'" in script.text:  
        print ('Headline found:\nIndex position %s' %idx)

5)说它处于索引位置1。所以让我们抓住:

scriptStr = scripts[1].text
print (scriptStr)

6)现在,我真正需要在<script>标签中进行搜索的是在文本中以'var CNN'开头的标签,因为这可能不会改变,而标题会,所以现在我可以回去了,而不是寻找标题子字符串,而是让它找到'var CNN'

...
tags = soup.find_all('script')
for tag in tags:
    if 'var CNN = CNN ||' in tag.text:
    ...
    ...

7)最后一部分(我不会讨论)是然后修剪掉其中的所有多余子字符串,以保留包含所有数据的有效json。找到并保留有效的json子字符串后,您可以使用json.loads()来读入,然后可以遍历python将其存储在其中的字典/列表。

答案 2 :(得分:0)

可能是因为您没有像这样初始化BeautifulSoup对象:

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

答案 3 :(得分:0)

基于您的初始代码:

import requests
import bs4
res = requests.get('https://edition.cnn.com/politics')
res.raise_for_status()
soup = bs4.BeautifulSoup(res.text)

我建议您在浏览器外部查看一下汤:

with open("cnn_site.txt", "w", encoding='utf-8') as f:
    f.write(soup.prettify())

快速分析表明,我们没有与浏览器相同的内容。 具体来说,当在文本文件中搜索h3时,您将找不到与浏览器开发人员工具中相同的内容。

这意味着当您使用浏览器打开网站时,javascript会触发完整的html。 但是当您使用请求时不会。

为确认这一点,我已经从浏览器中复制了已加载网站的,并将其复制到新的html文件中。

然后:

with open("cnn_body.html") as f:
    content = f.read()
soup = BeautifulSoup(content)
len(soup.find_all('h3'))
>>> 87

因此,需要在请求中添加一些内容以“触发”完整的html。 或者您可以解析内容。