- 本书赞誉
- 前言
- 目标读者
- 不适合阅读本书的读者
- 本书结构
- 什么是数据处理
- 遇到困难怎么办
- 排版约定
- 使用代码示例
- 致谢
- 第 1 章 Python 简介
- 第 2 章 Python 基础
- 第 3 章 供机器读取的数据
- 第 4 章 处理 Excel 文件
- 第 5 章 处理 PDF 文件 以及用 Python 解决问题
- 第 6 章 数据获取与存储
- 第 7 章 数据清洗:研究、匹配与格式化
- 第 8 章 数据清洗:标准化和脚本化
- 第 9 章 数据探索和分析
- 第 10 章 展示数据
- 第 11 章 网页抓取:获取并存储网络数据
- 第 12 章 高级网页抓取:屏幕抓取器与爬虫
- 第 13 章 应用编程接口
- 第 14 章 自动化和规模化
- 第 15 章 结论
- 附录 A 编程语言对比
- 附录 B 初学者的 Python 学习资源
- 附录 C 学习命令行
- 附录 D 高级 Python 设置
- 附录 E Python 陷阱
- 附录 F IPython 指南
- 附录 G 使用亚马逊网络服务
- 关于作者
- 关于封面
11.2 分析网页
网络抓取的大多数时间会花费在观察浏览器标记语言和搞清楚如何同它交互上。了解你最爱的浏览器的调试或开发工具是成为一名高级网页抓取者的必要环节。
根据浏览器的不同,工具可能有不同的名称和功能,但是概念是相同的。你需要自学最喜欢的浏览器工具,不管是 IE(https://msdn.microsoft.com/library/bg182326(v=vs.85))、Safari(https://developer.apple.com/safari/tools/)、Chrome(https://developer.chrome.com/devtools)或 Firefox(https://developer.mozilla.org/en-US/docs/Tools/GCLI)。
每个浏览器的调试器都是类似的。你会在一个区域看到请求和页面加载数据(通常叫网络或者其他类似的东西);在另外一个区域分析页面的标记信息,看到每个标签中的内容和样式(通常叫作检视、元素或 DOM)。在第三个区域,你可以看到 JavaScript 错误,并同页面中的 JavaScript 交互;这个区域通常叫作控制台。
你的浏览器开发者工具可能还有其他的标签,但是我们真的只需要这 3 个标签来理解页面是如何构建的,以及如何简单地抓取内容。
11.2.1 检视:标记结构
当你想要抓取一个站点的时候,首先分析站点结构和标记语言。像在第 3 章学到的那样, XML 的结构由节点和内容以及键和值组成。HTML 非常相似。如果打开浏览器的开发者工具,浏览检视(Inspection)、元素(Elements)或 DOM 标签,你会看到一系列的节点和它们的值。节点和其包含的数据同我们在 XML 示例中看到的有一些不同——它们是 HTML 标签(表 11-1 列出了一些基本的标签)。HTML 标签用来告诉你内容信息。如果你想找到页面上的所有图片,查找 img 标签。
表11-1:基本的HTML标签
标签 | 描述 | 示例 |
head | 用来存储元数据和文档的其他必需信息 | <head> <title>Best Title Ever</title> </head> |
body | 用来存储页面大部分内容 | <body> <p>super short page</p> </body> |
meta | 用来存储元数据,例如站点简短的描述或关键词 | <meta name="keywords"content="tags, html"> |
h1,h2,h3 … | 用来存储头部信息;数字越小,头部越大 | <h1>Really big one!</h1> |
p | 用来存储文本段落 | <p>Here's my first paragraph.</p> |
ul,ol | 用来存储无序表(ul:圆点)和有序表(ol:数字) | <ul><li>first bullet</li></ul> |
li | 用来存储列表对象;应该始终位于一个列表(ul 或 ol)中 | <ul><li>first</li> <li>second</li></ul> |
div | 用于分节或划分内容 | <div id="about"><p>This div is about things.</p></div> |
a | 用于链接内容,被称作“锚标签” | <a href="http://oreilly.com">Best Ever</a> |
img | 用于插入一张图片 | <img src="/flying_cows.png" alt="flying cows!" /> |
关于 HTML 标签和其使用方式的完整介绍,请查看 Mozilla 开发者网络的 HTML 参考、指南和介绍(https://developer.mozilla.org/en-US/docs/Web/HTML)。
除了使用的标签和内容结构,每个标签之间放置的位置很重要。类似于 XML,HTML 也有父元素和子元素。在结构中存在层次关系。父节点拥有子节点,而学习如何遍历家族树结构会帮助你得到想要的内容。了解元素之间的关系,无论它们是双亲节点、子节点还是同级节点,会帮助你编写更高效、快速和易于更新的抓取器。
让我们仔细地查看在 HTML 页面中这些关系意味着什么。下面是一个基本的 HTML 站点的结构。
<!DOCTYPE html> <html> <head> <title>My Awesome Site</title> <link rel="stylesheet" href="css/main.css" /> </head> <body> <header> <div id="header">I'm ahead!</div> </header> <section class="main"> <div id="main_content"> <p>This site is super awesome! Here are some reasons it's so awesome:</p> <h3>List of Awesome:</h3> <ul> <li>Reason one: see title</li> <li>Reason two: see reason one</li> </ul> </div> </section> <footer> <div id="bottom_nav"> <ul> <li><a href="/about">About</a></li> <li><a href="/blog">Blog</a></li> <li><a href="/careers">Careers</a></li> </ul> </div> <script src="js/myjs.js"></script> </footer> </body> </html>
如果从这个页面的第一个标签开始(文档类型声明下),可以看到整个页面的所有内容都在 html 标签下。html 标签是整个页面的根标签。
在 html 标签内,有标签 head 和 body。页面的大部分内容在标签 body 内,但是 head 也有一些内容。标签 head 和 body 是 html 元素的子标签。反过来,这些标签有着他们自己的子标签和后继标签。head 和 body 标签是同级关系。
查看主 body 标签的内部,可以看到其他一些家族关系。所有这些列表对象(li 标签)是无序列表(ul 标签)的子标签。header、section 和 footer 标签是同级关系。script 是 footer 的子标签,是 footer 中 div 标签的邻居,用来存储链接。还有很多复杂的关系,这只是一个简单的页面!
为了更深入地研究,下面的代码展示了一个有着更复杂关系的页面(处理网页抓取时,几乎很难有一个所有元素组织合理且关系完整的完美页面):
<!DOCTYPE html> <html> <head> <title>test</title> <link ref="stylesheet" href="/style.css"/> </head> <body> <div id="container"> <div id="content" class="clearfix"> <div id="header"> <h1>Header</h1> ➊ </div> <div id="nav"> ➋ <div class="navblock"> ➌ <h2>Our Philosophy</h2> <ul> <li>foo</li> <li>bar</li> </ul> </div> <div class="navblock"> ➍ <h2>About Us</h2> ➎ <ul> <li>more foo</li> ➏ <li>more bar</li> </ul> </div> </div> <div id="maincontent"> ➐ <div class="contentblock"> <p>Lorem ipsum dolor sit amet...</p> </div> <div class="contentblock"> <p>Nunc porttitor ut ipsum quis facilisis.</p> </div> </div> </div> </div> <style>...</style> </body> </html>
❶ 当前元素父元素的前面的邻居的第一个子元素。
❷ 当前元素的父级 / 祖先。
❸ 当前元素的邻居。
❹ 当前元素。
❺ 当前元素的第一个子元素 / 后代。
❻ 当前元素的子元素 / 后代。
❼ 当前元素父元素的下一个邻居。
为便于讨论,“当前元素”是第二个为 navblock 类的 div。可以看到它有两个子元素,一个是标题(h2),另一个是无序列表(ul),同时还有列表对象(li)位于列表中。它们是后代(取决于你想使用的库,它可能被包含在“all children”中)。当前元素有一个邻居,即第一个为 navblock 类的 div。
ID 为 nav 的 div 是当前元素的父元素,但是我们的元素有其他的祖先。如何从当前元素移动到 ID 为 header 的 div ?我们的父元素是 header 元素的邻居。为了得到 header 元素的内容,可以找到父元素的前面的邻居。父元素同样有另外一个邻居,即 ID 为 maincontent 的 div。
所有这些关系被描述为文档对象模型(DOM)结构。HTML 用规则和标准来组织页面上的内容(也被称作文档)。HTML 元素节点是“对象”,并且为了正确地展示,它们必需遵循一个模型 / 标准。
花费在理解节点之间的关系上的时间越多,使用代码快速有效地遍历 DOM 树就越容易。在本章之后的部分,我们会介绍 XPath,它使用家族关系选择内容。现在,进一步理解了 HTML 结构和 DOM 元素之间的关系之后,我们可以更仔细地研究定位和分析在选择的站点上想要抓取的内容。
你或许可以使用开发者工具搜索标记代码,这取决于浏览器。这是一种非常棒的查看元素结构的方式。举个例子,如果正在寻找内容中一个特别的小节,可以搜索这些词语,找到它们的位置。许多浏览器还允许你右击页面上的元素,选择“检视”。这通常会打开你的浏览器工具来选择元素。
我们会在示例中使用 Chrome,但是你可以使用自己最喜欢的浏览器。当研究非洲的童工时,我们发现了将童工与冲突相联系的数据。这促使我们找到致力于阻止非洲冲突地带和冲突矿产的组织。打开其中一个组织的页面:Enough 项目的 Take Action 页面(http://www.enoughproject.org/take_action)
当第一次打开开发者工具时——在 Chrome 中选择工具→开发者工具,在 IE 中敲击 F12,在 Firefox 中选择工具→网页开发者→检视器,或者在 Safari 高级设置中启用开发者菜单——我们会在一个面板中看到标记信息,在另外一个小的面板中看到 CSS 规则和样式,在工具上方的一个面板中看到真正的页面。对于不同的浏览器,布局可能会不同,但是用这些工具查看这些特性时应该会很相似(见图 11-2)。
图 11-2:Enough 项目的 Take Action 页面
如果移动光标到开发者工具的标记部分(检视标签),你可能会看到页面上不同的部分出现高亮。这是一个非常棒的特性,可以帮助你看到标记和页面结构中不同的元素。
如果点击与 div 和页面主元素相邻的箭头,你可以看到位于其中的元素(子元素)。举个例子,在 Enough 项目主页上,可以通过点击右边栏(在图 11-3 中圈出),打开 main-inner-tse div 和其他内部的 div。
图 11-3:探索侧边栏
可以看到侧边栏图片在链接之内,链接位于一个段落之中,而段落在 div 中——这个列表很长。理解什么时候图片在链接中(或不在),确定哪个内容位于段落标签中,以及找到其他页面结构元素是定位和抓取页面内容所必需的。
开发者工具的另外一个非常棒的用处是研究元素。如果右击页面的一部分,你应该会看到一个包括了一些有用的用于网页抓取的工具的菜单。图 11-4 展示了一个菜单的实例。
图 11-4:检视元素
如果点击“检视元素”选项,开发者工具应该会在源代码中打开那个元素。这是一个非常有用的特性,可用于与内容交互,以及查看其在代码中的位置。
除了能够在浏览器中同元素交互之外,你还可以在源代码部分同元素交互。图 11-5 展示了通过在标记结构区域右击一个元素得到的菜单类型。可以看到复制 CSS 选择器或 XPath 选择器(我们会在本章中使用这两者定位和抽取网站内容)的选项。
图 11-5:元素选项
对于不同的浏览器,工具的语言和交互可能差别很大,但是菜单选项应该同在这里描述的类似,这应该让你对如何访问数据和这些交互操作有了一些了解。
除了找到元素和内容之外,开发者工具展示了大量有关页面上节点结构和家族关系的信息。在开发者工具中,通常会有一个检视标签,展示当前元素的父元素列表。该列表中的元素通常可以被点击或者选择,所以你可以通过一个简单的点击遍历 DOM。在 Chrome 中,这个列表在开发者工具和上方页面之间的灰色部分。
我们已经查看了 Web 页面的组织结构,以及如何同它们交互来更好地理解内容的位置。现在来研究浏览器中让网页抓取更加简单的其他强大工具。
11.2.2 网络/时间线:页面是如何加载的
分析开发者工具中的时间线(Timeline)/ 网络(Network)标签将让你深刻理解页面内容是如何加载的以及加载的顺序。页面加载的时间和方式可以极大地影响你决定抓取页面的方式。有时,理解内容来源是抓取所需内容的“快捷方式”。
网络或时间线标签展示了已加载的 URL、加载顺序和加载所需时间。图 11-6 展示了 Enough 项目页面在 Chrome 中网络标签的样子。对于不同的浏览器,你可能需要重新加载页面,来查看网络标签页面的内容。
图 11-6:一个页面的网络标签
由于在网络标签中只有一个请求,可以看到整个页面在一次调用中加载完成。这对于网页抓取器来说是很棒的消息,因为这意味着通过一个请求可以获得一切。
如果点击这个请求,可以看到更多的选项,包括响应的源代码(见图 11-7)。当页面通过许多不同的请求加载时,查看每个请求的内容对于定位所需内容很重要。如果你需要额外的数据来加载站点,可以通过点击网络标签中的头部标签来研究头部和 cookie。
图 11-7:网络响应
来查看一个有着复杂网络标签的相似组织的页面。打开你的网络标签,使用浏览器访问 Fair phone 倡议站点上的#WeAreFairphone 页面(http://www.fairphone.com/we-are-fairphone/,图 11-8)。
图 11-8:有很多页面的网络标签
你可以立即看到这个页面正在处理更多的请求。点击每一个请求,你可以看到每一个请求加载的内容。请求顺序显示在网络标签中的时间线上。这可以帮助你理解如何抓取和处理页面,来得到需要的内容。
通过点击每一个请求,可以看到初始页面加载后再加载大部分内容。点击初始页面的请求,会发现并没有什么内容。我们想要问的第一个问题是:这里是否有一个 JavaScript 请求或其他的请求使用 JSON 加载内容?如果有的话,对于我们的脚本来说,这可能是一个恰当的“快捷方式”。
你知道如何解析和读取 JSON(第 3 章),所以如果你在网络标签中找到一个 URL,伴随着一个 JSON 响应,其中保存着你需要的数据,那么你可以使用这个 URL 来获得数据,之后直接从响应中解析数据。你需要意识到所有可能在请求中需要发送的头部(展示在网络标签中的头部小节),以得到正确的响应。
如果这里没有简单的 JSON URL 匹配你需要的信息,或者信息散落在几个不同的的请求中,需要人工整合它们到一起,那么可以确定,你需要使用一个基于浏览器的方法来抓取站点。基于浏览器的网页抓取允许你读取看到的页面,而不仅是每一个请求。如果你需要在正确抓取内容之前同一个下拉菜单交互,或执行一系列基于浏览器的操作,这可以很有用。
网络标签帮助你找到包含所需内容的请求,以及是否有优秀的备选数据源。我们接下来会查看 JavaScript,看看这是否也会提供一些关于抓取器的想法。
11.2.3 控制台:同JavaScript交互
现在已经分析了页面的标记和结构,以及页面加载和网络请求的时间线,让我们转到 JavaScript 控制台,来看一下通过和运行在页面上的 JavaScript 交互,可以学到什么。
如果你已经对 JavaScript 很熟悉,使用起来应该相当简单;如果你从未同 JavaScript 交互过,花一些时间查看关于 JavaScript 课程的介绍(http://www.codecademy.com/en/tracks/javascript)会很有用。你只需要理解 JavaScript 的基本语法,能够通过控制台同页面上的元素交互。我们会从学习 JavaScript 和基本的样式开始,学习如何使用控制台界面。
01. 样式基础
每一个网页都会使用一些样式元素来帮助它组织内容、控制内容的大小和颜色,并在视觉上修改内容。当浏览器开始开发 HTML 标准,样式标准也就诞生了。样式标准的产物是级联样式表,即 CSS,这为我们提供了给页面添加样式的标准方式。举个例子,如果想要所有的标题使用不同的字体,或所有的照片在页面居中显示,你需要在 CSS 中编写这些规则。
CSS 允许样式级联,或者从父样式和样式表中继承。如果我们为整个站点定义一个样式集合,内容管理系统将很容易让每个页面看起来相似。即使我们有一个复杂站点,它有很多不同的页面类型,我们也可以定义一个主要的 CSS 文档和几个次要的文档,在页面需要额外的样式时加载次要文档。
CSS 有效,因为它定义了允许通过标签中的属性组合 DOM 元素的规则。是否记得在第 3 章研究 XML 时,讨论过嵌套属性? CSS 同样使用这些嵌套的属性。让我们学习使用元素检视工具。因为你很可能仍然在 Fairphone 站点,所以让我们看一些页面上的 CSS 属性。当在底部工具栏高亮一个元素时,会看到一些与页面中元素相关的文本展示在旁边(图 11-9)。
图 11-9:CSS 简介
在这个例子中,我们已经了解了 div 的含义,但是什么是 contont-block ?使用检视技术看一下 HTML 代码(右击页面上的元素,选择“检视元素”)。
我们看到 content-block 是 CSS 类(在图 11-10 中的嵌套属性 class="content-block"中)。它定义在一个起始 div 标签中,同时这个 div 保存着所有其他子标签。说到 CSS 类,在页面的这个部分中,你能看到多少个类?有好多啊!
图 11-10:CSS 类
像类一样,同样有 CSS ID。让我们找一个(见图 11-11),看看它与类有什么不同。
图 11-11:CSS ID
HTML 看起来很类似,但是在导航栏的符号中使用了一个哈希或英镑符号。#是一个适用于 ID 的 CSS 选择器。对于类,我们使用 .(如同在 div.content-block 中展示的)。
CSS 结构和语法要求 id 必需是唯一的,但是你可以有很多的元素有相同的 class。尽管页面不总是符合这一结构,但是这也值得注意。一个 CSS id 相对于一个 class 有更大的特异性。一些元素不只有一个 class,所以它们可以应用多个样式。
使用右击菜单,在页面上复制 CSS 选择器相当简单。如果你已经了解了 CSS,这些知识会帮助你进行网页抓取。如果你不是太了解 CSS,但是希望更深入地探索它,可以查看 Codecademy 关于 CSS 课程的介绍(https://www.codecademy.com/courses/web-beginner-en-TlhFi/0/1)或查看 Mozilla 开发者网络的参考和指南(https://developer.mozilla.org/en-US/docs/Web/CSS)。
现在我们进一步了解了 CSS,了解了它是如何样式化页面的;但是你可能会问,在浏览器终端中 CSS 需要做什么?好问题!让我们回顾一下 jQuery 和 JavaScript 的基础知识,这样可以看到 CSS 是如何同页面上的内容交互的。
02. jQuery和JavaScript
JavaScript 和 jQuery 的演化历史要比 HTML 和 CSS 悠久得多,一部分原因是 JavaScript 的开发在很长一段时间里没有一整套的标准。从某种意义上来讲,JavaScript 是(在某种程度上仍然是)网站版图上一片荒芜的西部风光。
即使 JavaScript 在过去的 10 年间改变了很多,但有 10 年历史的脚本仍然经常运行在一些浏览器中,这意味着推进标准化(如何编写 JavaScript 和哪些事在 JavaScript 中不允许)的进程相对于 HTML 和 CSS 有些慢。
JavaScript 不是标记语言,而是一门脚本语言。因为 Python 也是一门脚本语言,所以你可以将一些已经学过的东西——函数、对象、类、方法——应用到对 JavaScript 的理解中去。同 Python 一样,有其他的库和包帮助你编写清晰、简单和高效的 JavaScript 代码,以便于浏览器和人们理解。
jQuery(https://jquery.com/)是一个 JavaScript 库,很多大型的网站使用它以期让 JavaScript 更易读、编写更简单,同时仍然允许浏览器(和它们不同的 JavaScript 引擎)来解析脚本。
早在 2005~2006 年,jQuery 引入了简化和标准化 JavaScript 的想法,为 JavaScript 开发者提供了工具,这样他们就不需要从零开始编写所有的代码。 jQuery 的确推动了 JavaScript 向前发展,通过强大而易于解释的方法,以及在选择页面元素时与 CSS 更紧密的联系,创建了一个更加面向对象的方法。
自从 jQuery 被开发之后,JavaScript 和 CSS 的关系便更加紧密了,并且很多新的 JavaScript 框架都基于这个面向对象的方法。如果一个站点正在运行 jQuery,使用 CSS 标识符同页面上的元素交互很简单。假如我们想要从#WeAreFairphone 页面(图 11-12)上正在查看的 content-block 类抓取内容,通过 JavaScript 控制台该如何实现呢?
图 11-12:jQuery 控制台
由于网站正在运行 jQuery,直接在控制台标签第一行中输入下面的代码:
$('div.content-block');
敲击回车键,控制台会响应那个元素。点击控制台中的响应,你会看到子元素,以及该元素的子元素。可以同一些基本的 jQuery(例如,$(elem);)一起使用 CSS 选择器,来选择其他页面上的元素。使用 $ 和括号会告诉 jQuery 我们正在寻找一个与括号中的字符串传递的选择器相匹配的元素。
你能使用控制台来选择 ID 为 weAreFairphone 的 div 吗?你是否只能选择页面上的锚标记(a)?在控制台中尝试一下。命令行和 jQuery 提供了一个简单的方式来使用 CSS 选择器或标签名称同页面上实际的元素交互,并从这些元素中拉取内容。但是这与 Python 有什么关系呢?
因为 jQuery 改变了人们对 CSS 选择器用处的看法,Python 抓取库现在使用这些选择器来遍历和寻找网页中的元素。就像你可以在浏览器控制台中使用简单的 jQuery 选择器,你也可以在 Python 抓取器代码中使用它。如果想学习更多的 jQuery 知识,建议你访问 jQuery 学习中心(https://learn.jquery.com/),或在 Codecademy(http://www.codecademy.com/en/tracks/jquery)或 Code School(https://www.codeschool.com/courses/try-jquery)上学习课程。
如果碰到一个没有使用 jQuery 的网站,那么 jQuery 在你的控制台中就不会工作。为了只使用 JavaScript 通过类来选择元素,运行:
document.getElementsByClassName('content-block');
你应该看到相同的 div,并且能够通过相同的方式在控制台中浏览。现在你大致知道了可以利用的工具,所以让我们更仔细地看一下如何确定抓取页面中感兴趣内容的最佳方式。首先,我们会学习如何研究页面中所有的部分。
11.2.4 页面的深入分析
一个开发 Web 抓取器的好方式是先在浏览器中分析内容。首先选择你最感兴趣的内容,并且在浏览器检视或 DOM 标签中观察。数据是如何组成的?哪里是父节点?内容包含在许多元素中,还是少量元素中?
在开始抓取一个页面之前,通过查看内容的限制信息和站点的 robots.txt 文件,来查看自己是否有权利抓取这个页面。你可以输入域名,随后输入 / robots.txt 找到这个文件(例如,http://oreilly.com/robots.txt)。
之后移向网络 / 时间线标签(见图 11-6)。页面的第一次加载看起来是什么样子?页面加载中是否使用了 JSON ?如果是的话,文件看起来什么样子?是否大部分内容在初次请求之后加载?所有的这些答案会帮助你确定要使用哪种类型的抓取器,以及抓取该页面有多困难。
然后,打开控制台标签。尝试使用你检视得到的信息,同包含重要内容的元素交互。对于这个内容,编写一个 jQuery 选择器有多简单?在整个域名下,你的选择器有多可靠?你是否可以打开一个类似的页面,使用该选择器,并得到类似的结果?
如果在 JavaScript 控制台中使用 jQuery 或 JavaScript 很容易与你的内容交互,那么使用 Python 处理可能也会很简单。如果使用 jQuery 选择一个元素很困难,或者在一个页面上可以使用的代码在另一个相似的页面上不起作用,很可能在 Python 中也同样困难。
不能使用 Python 工具正确解析的网页少之又少。我们会教给你一些技巧,来应对混乱的网页、内联 JavaScript、格式化糟糕的选择器,以及你能在万维网的代码中发现的所有糟糕的选择,同时还会给出一些最佳实践。首先,看看加载和读取网页。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论