python 抓取新闻_我用100行Python创建了一个新闻抓取工具。

python 抓取新闻

每天我乘地铁去我的办公室,我的手机根本没有信号。 但是中等的应用程序不允许我离线阅读故事,因此我决定自己制作一个新闻抓取工具。

我不想做一个花哨的应用程序,所以我只完成了可以满足我的需求的最小原型。 这个概念很简单:

  1. 寻找一些新闻来源
  2. 使用Python抓取新闻页面
  3. 解析html并使用BeautifulSoup提取内容
  4. 将其转换为可读格式,然后发送电子邮件给我自己

现在让我解释一下我是如何做的。

新闻来源:Reddit

人们提交指向Reddit的链接并对其进行投票,因此Reddit是阅读新闻的好消息来源。 现在的问题是:如何每天获取热门新闻列表?

在考虑进行网络抓取之前,我们应该尝试确定目标网站是否提供任何API,因为使用API​​是完全合法的,而且最重要的是,API提供了机器可读数据,因此我们无需解析HTML。

幸运的是,Reddit提供了API。 从其API列表中,我们可以轻松找到所需内容: /top 。 该端点将在Reddit或给定的subreddit上返回最新新闻。 这样我们就可以使用它从感兴趣的子索引中检索最新新闻。

下一个问题是:我们如何访问此API?

阅读Reddit文档后,我找到了访问此端点的最佳方法。

第一步是在Reddit上创建一个应用程序。 登录到我的帐户,然后转到“首选项→应用程序”。 底部有一个名为“创建另一个应用程序…”的按钮。 单击它并创建一个“脚本”类型的应用程序。 请注意,我们不需要提供“关于URL”或“重定向URL”,因为我们的应用程序无意公开或其他任何人访问。

创建应用后,我们可以在应用信息框中找到应用ID和密码。

下一个问题是如何使用此凭据。 由于我们只需要获取给定subreddit的最新消息,因此我们不需要访问任何与用户相关的信息,因此从技术上讲,我们不需要提供任何用户信息,例如用户名或密码。 Reddit提供了“ 仅应用程序OAuth ”,通过它我们的应用程序可以匿名访问公共信息。 尝试使用以下命令来测试API:

$ curl -X POST -H 'User-Agent: myawesomeapp/1.0' -d grant_type=client_credentials --user 'OUR_CLIENT_ID:OUR_CLIENT_SECRET'   https://www.reddit.com/api/v1/access_token 

我们将获得如下访问令牌:

{"access_token": "ABCDEFabcdef0123456789", "token_type": "bearer", "expires_in": 3600, "scope": "*"}

优秀的! 使用访问令牌,我们可以做任何事情。

最后,我们不想从头开始编写API访问代码。 我们可以使用python客户端库:

让我们做一个快速测试。 我们从/r/Python获取前5个提交:

>>> import praw 
>>> import pprint
>>> reddit = praw.Reddit(client_id='OUR_CLIENT_ID',
... client_secret='OUR_SECRET',
... grant_type='client_credentials',
... user_agent='mytestscript/1.0')
>>> subs = reddit.subreddit('Python').top(limit=5)
>>> pprint.pprint([(s.score, s.title) for s in subs])
[(6555, 'Automate the boring stuff with python - tinder'),
(4548,
'MS is considering official Python integration with Excel, and is asking for '
'input'),
(4102, 'Python Cheet Sheet for begineers'),
(3285,
'We started late, but we managed to leave Python footprint on r/place!'),
(2899, "Python Section at Foyle's, London")]

大!

刮新闻页面

下一步非常简单。 从最后一步,我们可以获取Submission对象,其url属性正是我们想要的URL。 我们还可以通过检查domain属性来过滤URL,以确保仅从Reddit以外的网站上抓取链接。

subs = [sub for sub in subs if not sub.domain.startswith('self.')]

接下来,我们要做的就是获取URL。 这可以通过requests完成:

for sub in subs:
res = requests.get(sub.url)
if (res.status_code == 200 and 'content-type' in res.headers and
res.headers.get('content-type').startswith('text/html')):
html = res.text

在这里,我们跳过了内容类型不是text/html的提交。 这是因为用户可能会提交不是我们目标的直接图像链接。

提取内容

下一步是从新闻HTML中提取文本内容。 我们的目标是提取新闻标题和新闻内容,并忽略诸如页眉,页脚,侧边栏之类的内容或不需要阅读的内容。

这是一项艰巨的任务,老实说,还没有通用,完美的解决方案。 我们可以使用BeautifulSoup提取文本内容,但是它将提取所有内容,包括页眉和页脚。

我很幸运地发现,现代网站的格式要比其祖先的格式好。 我们不再看到表格布局以及<font><br> ,而是在文章页面上为标题清楚地标记了<h1> ,为每个段落都标记了<p> 。 我发现大多数网站会将标题和主要内容放在同一容器元素中,例如:

<header>Site Navigation</header>
<div id="#main">
<section>
<h1 class="title">Page Title</h1>
</section>
<section>
<p>Paragraph 1</p>
<p>Paragraph 2</p>
</section>
</div>
<aside>Sidebar</aside>
<footer>Copyright...</footer>

在这里,顶层<div id="#main">是标题和内容的通用容器。 因此,我们可以制定一种算法来查找内容:

  1. 查找<h1>作为标题。 通常,出于SEO的目的,页面中只有一个<h1>
  2. 查找<h1>的父级,并测试该父级是否具有足够的<p>元素。
  3. 重复步骤2,直到找到具有足够<p>元素的父元素或到达<body>标签。 如果找到足够的<p> ,则父元素是主要的内容标签。 如果在找到足够的<p>之前已到达<body>标记,则该页面不包含可读内容。

这是一个简单的算法,没有考虑任何语义信息,但实际上对于我们的目的来说效果很好。 无论如何,如果此算法失败,我们可以简单地忽略新闻,这对我来说少读一点新闻也#main ……您肯定可以通过解析<header><footer>#main.sidebar制作更准确的算法ID /类。

使用此算法,我们可以轻松编写解析器代码:

soup = BeautifulSoup(text, 'html.parser')
# find the article title
h1 = soup.body.find('h1')
# find the common parent for <h1> and all <p>s.
root = h1
while root.name != 'body' and len(root.find_all('p')) < 5:
root = root.parent
if len(root.find_all('p')) < 5:
return None
# find all the content elements.
ps = root.find_all(['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre'])

在这里,我们使用len(root.find_all('p')) < 5作为主要内容的条件,因为真正的新闻不太可能少于5个段落。 您可以根据需要增加此值。

转换为可读格式

最后一步是将内容转换为可读格式。 在此示例中,我选择了Markdown,但您可以自己制作花哨的转换器。

在此示例中,我仅获取了<h#><p><pre>因此快速功能可以轻松地将它们转换为Markdown。

ps = root.find_all(['h2', 'h3', 'h4', 'h5', 'h6', 'p', 'pre'])
ps.insert(0, h1) # add the title
content = [tag2md(p) for p in ps]

def tag2md(tag):
if tag.name == 'p':
return tag.text
elif tag.name == 'h1':
return f'{tag.text}\n{"=" * len(tag.text)}'
elif tag.name == 'h2':
return f'{tag.text}\n{"-" * len(tag.text)}'
elif tag.name in ['h3', 'h4', 'h5', 'h6']:
return f'{"#" * int(tag.name[1:])} {tag.text}'
elif tag.name == 'pre':
return f'```\n{tag.text}\n```'

放在一起

最后是完整的代码:

恰好100行! 尝试运行它…

Scraping /r/Python...
- Retrieving https://imgs.xkcd.com/comics/python_environment.png
x fail or not html
- Retrieving https://thenextweb.com/dd/2017/04/24/universities-finally-realize-java-bad-introductory-programming-language/#.tnw_PLAz3rbJ
=> done, title = "Universities finally realize that Java is a bad introductory programming language"
- Retrieving https://github.com/numpy/numpy/blob/master/doc/neps/dropping-python2.7-proposal.rst
x fail or not html
- Retrieving http://www.thedurkweb.com/sms-spoofing-with-python-for-good-and-evil/
=> done, title = "SMS Spoofing with Python for Good and Evil"
...

和刮擦的新闻:

然后,我要做的最后一件事是将该脚本放在服务器上,设置cron作业以每天运行一次,然后将生成的文件发送到我的电子邮件中。

我没有在细节上花费太多的精力,因此仍有很多地方可以改进。 您可以继续向此脚本添加更多功能,例如使结果文件更漂亮并提取图像。

谢谢阅读! 希望此脚本有用,至少可以作为访问Reddit API的示例。 如果您喜欢,请推荐这篇文章。

翻译自: https://hackernoon.com/i-made-a-news-scrapper-with-100-lines-of-python-2e1de1f28f22

python 抓取新闻

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值