打开APP
userphoto
未登录

开通VIP,畅享免费电子书等14项超值服

开通VIP
使用 BeautifulSoup 和 Selenium 进行网页爬取

翻译:  ZICK_ZEON, xiaoaiwhc1, 边城     链接:

https://www.oschina.net/translate/modern-web-scraping-with-beautifulsoup-and-selenium--cms


概述

HTML几乎是平铺直叙的。CSS是一个伟大的进步,它清晰地区分了页面的结构和外观。JavaScript添加一些魅力。道理上讲是这样的。现实世界还是有点不一样。

在本教程中,您将了解在浏览器中看到的内容是如何实际呈现的,以及如何在必要时进行抓取。特别是,您将学习如何计算Disqus评论。我们的工具是Python和这门语言的很棒的包,比如request、BeautifulSoup和Selenium。

什么时候应该使用网页爬取?

网页爬取是一种自动获取被设计于实现人工用户交互式网页的内容、解析它们并提取一些信息(可能是导航到其他页面的链接)的实践。如果没有其他方法来提取必要的网页信息时,网页爬取是很必要有效的技术方法。理想情况下,应用程序依靠提供好的专用API来编程自动获得网页的数据。可在下面几种场所景之下你最好就别用网页抓取技术了:

  • 被爬取的网页是脆弱的(您正在爬取的网页可能会被频繁更改)。

  • 爬取被禁止(一些web应用程序有禁止爬取的策略)。

  • 爬取速度可能会很慢和爬取内容过于繁杂的(如果你需要在很多无用信息中寻找和涉猎你想要的东东)。

了解真实的网页

让我们通过查看一些常见web应用程序代码的实现情况,来了解我们面临的问题。例如在“Vagrant技术入门”(链接:https://code.tutsplus.com/tutorials/introduction-to-vagrant--cms-25917)这篇帖子的页面底部有一些Disqus的评论:

为了爬取这些评论,我们需要首先在页面上找到它们。

查看页面代码

自20世纪90年代以来,每个浏览器都支持查看当前页面的HTML代码。下面是在源码视图下观看到的是“Vagrant技术入门”这篇帖子对应的源码内容的一个片段,这篇源码以大量与本文本身内容无关的被压缩过的和丑陋的JavaScript代码开始。下面是其中的一”小“部分:

这是页面中的一些实际HTML代码:

代码看起来乱糟糟,你竟然在页面的源代码中找不到Disqus评论,这让你有些吃惊。

强大的内联框架

原来页面是一个”混搭“, Disqus评论被嵌入到iframe(内联框架)元素中。你可以通过右键点击评论区域找到它,你会看到那里有框架信息和源码:

这是有意义的。将第三方内容嵌入iframe是使用iframe的主要应用场景之一。让我们在主页源中找到iframe标记。完蛋了!主页源中没有iframe标记。  

JavaScript-Generated标记

这个遗漏的原因是view page source显示了从服务器获取的内容。但是,由浏览器呈现的最终DOM(文档对象模型)可能非常不同。JavaScript开始工作,可以随意操纵DOM。无法找到iframe,因为从服务器检索页面时,它就是不存在。

静态抓取 vs. 动态抓取

静态抓取会忽略 JavaScript, 它可以不依靠浏览器而直接从服务器端获取网页代码. 这就是你通过'查看源码'所看到的东西, 然后你就可以进行信息提取了. 如果你要查找的内容已经存在于源码中, 那就不需要进一步的动作了. 可是, 如果你要查找的内容像上文的 Disqus 评论一样被嵌入iframe 中, 你就必须使用动态爬取来获取内容.

动态爬取使用一个真实的浏览器(或无界面浏览器), 它先让页面内的 JavaScript 运行起来, 完成动态内容处理加载. 之后, 它再通过查询 DOM 来获取所要寻找的内容. 有时候, 你还需要让浏览器自动模拟人的操作来得到你所需要的内容.

使用 Requests 和 BeautifulSoup 进行静态抓取

让我们来看看如何使用 Python 的两个经典包来进行静态抓取: requests 用来抓取网页内容. BeautifulSoup用来解析 HTML.

安装 Requests 和 BeautifulSoup

首先安装 pipenv, 然后运行命令: pipenv install requests beautifulsoup4

它首先为你创建一个虚拟环境, 然后安装这两个包在虚拟环境里. 如果你的代码在gitlab上, 你可以使用命令 pipenv install 来安装.

获取网页内容

用 requests 抓取网页内容只需要一行代码: r = requests.get(url).

代码返回一个 response 对象, 它包含大量有用的属性. 其中最重要的属性是 ok 和 content. 如果请求失败, r.ok 为 False 并且 r.content 包含该错误信息. content 代表一个字节流, 做文本处理时, 你最好将它解码成 utf-8.

>>> r = requests.get('http://www.c2.com/no-such-page')
>>> r.ok
False
>>> print(r.content.decode('utf-8'))


404 Not Found

Not Found


The requested URL /ggg was not found on this server.





Apache/2.0.52 (CentOS) Server at www.c2.com Port 80

如果代码正常返回没有报错, 那 r.content 会包含请求的网页源码(就是'查看源码'所看到的内容).

用 BeautifulSoup 查找元素

下面的 get_page() 函数会获取给定 URL 的网页源码, 然后解码成 utf-8, 最后再将 content 传递给 BeautifulSoup 对象并返回, BeautifulSoup 使用 HTML 解析器进行解析.

def get_page(url):
   r = requests.get(url)
   content = r.content.decode('utf-8')
   return BeautifulSoup(content, 'html.parser')

我们获取到 BeautifulSoup 对象后, 就可以开始解析所需要的信息了.

BeautifulSoup 提供了很多查找方法来定位网页中的元素, 并可以深入挖掘出嵌套的元素.

Tuts+ 网站包含了很多培训教程, 这里(https://tutsplus.com/authors/gigi-sayfan)是我的主页. 在每一个页面包含最多12篇教程, 如果你已经获取了12篇的教程, 你就可以进入下一页面了. 每一篇文章都被

标签包围着. 下面的函数就是发现页面里的所有 article 元素, 然后找到对应的链接, 最后提取出教程的 URL.

page = get_page('https://tutsplus.com/authors/gigi-sayfan')
articles = get_page_articles(page)
prefix = 'https://code.tutsplus.com/tutorials'
for a in articles:
   print(a[len(prefix):])  
   
   
Output:
building-games-with-python-3-and-pygame-part-5--cms-30085
building-games-with-python-3-and-pygame-part-4--cms-30084
building-games-with-python-3-and-pygame-part-3--cms-30083
building-games-with-python-3-and-pygame-part-2--cms-30082
building-games-with-python-3-and-pygame-part-1--cms-30081
mastering-the-react-lifecycle-methods--cms-29849
testing-data-intensive-code-with-go-part-5--cms-29852
testing-data-intensive-code-with-go-part-4--cms-29851
testing-data-intensive-code-with-go-part-3--cms-29850
testing-data-intensive-code-with-go-part-2--cms-29848
testing-data-intensive-code-with-go-part-1--cms-29847
make-your-go-programs-lightning-fast-with-profiling--cms-29809

使用 Selenium 动态爬取

静态爬取很适合一系列的文章,但正如我们前面看到的,Disqus 的评论是由 JavaScript 写在一个 iframe 中的。为了获取这些评论,我们需要让浏览器自动与DOM 交互。做这种事情最好的工具之一就是 Selenium。

Selenium 主要用于 Web 应用自动化测试,但它也是一个不错的通用浏览器自动化工具。

安装 Selenium

用这个命令安装 Selenium:pipenv install selenium

选择你的 Web 驱动

Selenium 需要一个 Web 驱动(自动化用的浏览器)。对于网页爬取来说,一般不需要在意选用哪个驱动。我建议使用 Chrome 驱动。Selenium 手册中有相关的介绍。

对比 Chrome 和 PhantomJS

某些情况下你可能想用没有用户界面的(headless)浏览器。理论上来说,PhantomJS 正好就是那款 Web 驱动。但是实际上有人报告一些只会在 PhantomJS 中出现的问题,这些问题在 Selenium 使用 Chrome 或 Firefox 时并不会出现。我喜欢从等式中删除这一变量,使用实际的 Web 浏览器驱动。

统计 Disqus 评论数量

我们来搞点动态抓取,使用 Selenium 统计 Tuts+ 手机的 Disqus 评论数量。下面需要导入的内容。

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.expected_conditions import (
   presence_of_element_located)
from selenium.webdriver.support.wait import WebDriverWait

get_comment_count() 函数需要传入 Selenium 驱动和 URL 作为参数。它使用驱动的 get() 方法从 URL 获取内容。这和requests.get()相似,其不同之处在于使用驱动对象管理 DOM 的实时呈现。

然后,它获取教程的标题,并使用 iframe 的父级 id,disqus_thread,和 iframe 标签来定位 iframe:

def get_comment_count(driver, url):
   driver.get(url)
   class_name = 'content-banner__title'
   name = driver.find_element_by_class_name(class_name).text
   e = driver.find_element_by_id('disqus_thread')
   disqus_iframe = e.find_element_by_tag_name('iframe')
   iframe_url = disqus_iframe.get_attribute('src')

接下来获取 iframe 的内容。注意我们要等到 comment-count 元素出现,因为评论是动态加载的,不一定可用。

driver.get(iframe_url)
wait = WebDriverWait(driver, 5)
commentCountPresent = presence_of_element_located(
   (By.CLASS_NAME, 'comment-count'))
wait.until(commentCountPresent)

comment_count_span = driver.find_element_by_class_name(
   'comment-count')
comment_count = int(comment_count_span.text.split()[0])

最后部分是返回最新的评论, 当然不包括我自己的评论. 方法是检查我还没有回复的评论.

last_comment = {}
if comment_count > 0:
   e = driver.find_elements_by_class_name('author')[-1]
   last_author = e.find_element_by_tag_name('a')
   last_author = e.get_attribute('data-username')
   if last_author != 'the_gigi':
       e = driver.find_elements_by_class_name('post-meta')
       meta = e[-1].find_element_by_tag_name('a')
       last_comment = dict(
           author=last_author,
           title=meta.get_attribute('title'),
           when=meta.text)
return name, comment_count, last_comment

结论

网页爬取是一个非常实用的技术, 尤其当你需要处理的信息浏览器并不提供有用的API支持的时候. 它通常需要一些技巧来从现代web应用中提取信息, 不过一些成熟的、设计良好的工具, 比如: requests、BeautifulSoup、Selenium 都会减轻你的工作并提高效率.

最后, 你可以试一下我写的一些工具, 它们在 Envato Market 有售, 欢迎提问和反馈.

(完)


看完本文有收获?请转发分享给更多人

关注「Python那些事」,做全栈开发工程师

本站仅提供存储服务,所有内容均由用户发布,如发现有害或侵权内容,请点击举报
打开APP,阅读全文并永久保存 查看更多类似文章
猜你喜欢
类似文章
【热】打开小程序,算一算2024你的财运
成功解决404 Not Found Not Found The requested URL was not found on the server. If yo
"error: device not found" and "error:device o...
aclocal: command not found
404 not found
解决adb调试报错error:device not found
The requested URL /test.php was not found on this server
更多类似文章 >>
生活服务
热点新闻
分享 收藏 导长图 关注 下载文章
绑定账号成功
后续可登录账号畅享VIP特权!
如果VIP功能使用有故障,
可点击这里联系客服!

联系客服