第3章


Python爬虫技术

在大数据时代,人类社会的数据种类和规模“爆炸式”增长,数据已经渗透到每一个行业
和业务领域,挖掘数据背后的知识和价值成为热门研究课题。那么数据从哪里来
? 
可以在
网络上搜索企业或政府公开的数据,但公开数据大部分针对某个领域,而且数据量较小,很
难满足多样化的数据需求;可以从数据平台购买数据,这样成本比较高。因此,如果想获取
多样化的、成本又比较低的数据,可以使用网络爬虫技术获取数据。本章主要介绍Python 
爬虫获取数据的基础知识和常用方法,重点讲解网页下载库Requests和网页解析库lxml 
及XPath语法。最后将爬虫技术应用于综合实战项目中爬取“北京链家网”的租房数据,爬
取的数据用于后续章节的数据分析及数据挖掘。

本章学习目标

● 
了解网络爬虫的基本概念。
● 
了解爬虫的合法性和robots协议。
● 
理解网络爬虫的工作原理
。
掌握网页下载器Requests库的使用
。
●

● 
掌握网页解析器lxml库的使用。
● 
掌握网络爬虫在综合实战项目中的应用。
● 
掌握多线程的数据爬取。

本章思维导图


3.案例导入
1 

本书综合实战项目为房屋租金数据的获取、分析与挖掘,从本章开始逐步实现该项目的
任务要求。首先,该实战项目的数据来自于“北京链家网”的租房数据,网址为htps://bj. 
liniczfn/。

aja.om/uag
综合实战项目数据获取任务要求如下。
从“北京链家网”爬取租房数据的主要信息,包括城区名(district)、街道名(stret )、小区

名(cnty)、for)、有无电梯(it)、面积(ra)、房屋朝向(owad)、户型

ommui楼层信息(lolfaetr

(l)和租金(t)等。
moderen

3.认识爬虫
2 

3.1 
爬虫的基本概念
2.
网络爬虫,又称为网页蜘蛛、网络机器人,是按照一定的规则,自动地抓取网页数据的程
序或者脚本。浏览器的网页内容由HTML 、JS 、CSS等组成,爬虫就是通过分析HTML代


码,从中获取想要的文本、图片及视频等资源。

(1)爬虫的分类。根据实现技术的不同,爬虫一般分为4种类型:通用爬虫、聚焦爬虫、
增量式爬虫、深层爬虫。实际的网络爬虫系统通常是几种爬虫技术相结合实现的。
① 通用爬虫:又称全网爬虫,爬取目标是整个Web,数据量庞大。这种爬虫对爬取速
度和存储要求比较高,主要应用于大型搜索引擎,提供大而全的内容来满足各种不同需求的
用户。
② 聚焦爬虫:又称主题爬虫,选择性爬取与预先定义好的主题相关的网页。聚焦爬虫
只爬取某些主题的页面,大大节省爬虫运行时所需的网络资源和硬件资源。
③ 增量式爬虫:在爬取网页的时候,只爬取内容发生变化的网页或者新产生的网页, 
对于未发生内容变化的网页,则不会爬取。它在一定程度上保证所爬取的网页是尽可能新
的网页。例如,电影网站会更新热门电影,增量式爬虫监测此网站的电影更新数据,抓取更
新后的页面数据。增量式爬虫抓取的数据量变小了,但爬行算法的复杂度和实现难度有所
提高。
④ 深层爬虫:爬取目标是互联网中深层页面的数据。网站页面分为表层页面和深层
页面,表层页面是指使用超链接可以到达的静态网页为主构成的Web页面。深层页面是指
不能通过静态链接获取的、隐藏在搜索表单后的、只有用户提交一些关键词才能获得的
Web页面。例如,用户注册后内容才可见的网页就
属于深层页面,深层爬虫就是爬取深层页面的内容。

(2)爬虫的结构。网络爬虫的主要任务是从网
页上抓取目标数据。为了完成这一任务,一个简单的
爬虫主要由四个部分组成,分别是URL管理器、网页
下载器、网页解析器、数据存储器。爬虫的基本结构
如图3-1所示。
①URL管理器:主要功能是存储和获取URL 

地址以及URL去重。包括待爬取的URL队列和已

爬取的URL队列,防止重复抓取URL和循环抓

取URL 。

爬虫从待抓取的URL队列中读取一个URL,将URL对应的网页下载下来,将URL 
放进已抓取的URL队列,如果从已下载的网页中解析出其他URL,和已抓取的URL进行
比较去重,将不重复的URL放入待抓取的URL队列,从而进入下一个循环。

② 网页下载器:主要功能是下载网页内容。将目标URL地址所对应的网页下载到本
地,然后将网页转换成一个字符串。实现HTTP请求功能常用的两个库:urlib库和
Requests库。urlib库是Python官方基础模块,可以直接调用。Requests库是一个第三方
库,功能丰富,使用方便,本书主要使用Requests库。
③ 网页解析器:主要功能是解析网页内容。将一个网页字符串进行解析,提取出有用
的信息。常用的网页解析器有正则表达式、lxml库和BeautifulSoup库。
④ 数据存储器:主要功能是存储数据。将网页解析器提取的信息保存到文件或数据
库中。
图3-1爬虫的基本结构图

5 3 
网页下载器和网页解析器是爬虫的两个核心部分,3.3节和3.4节详细介绍它们的工作
过程。
3.2.2 爬虫的工作流程
爬虫的工作流程如图3-2所示。
图3-2 爬虫的工作流程
(1)Request:发起请求。爬虫首先通过HTTP库,向待爬取的URL地址对应的目标
站点发起请求,即发送Request,等待服务器响应。
(2)Response:获取响应。如果服务器正常工作,收到请求后,根据发送内容做出响
应,然后将响应内容(response)返回给请求者。返回的内容一般是爬虫要抓取的网页内容, 
例如HTML、二进制文件(视频、音频)、文档、JSON 字符串等。
(3)解析内容。爬虫利用正则表达式、页面解析库(lxml、BeautifulSoup等)提取目标
信息。
(4)保存数据。解析得到的数据保存到本地,可以以文本、音频、视频等多种形式保存。
3.2.3 爬虫的合法性与robots协议
如果一个网站想限制爬虫,一般有两种方式。一种方式是来源审查,即通过User- 
Agent进行限制,只响应某些浏览器或友好爬虫的访问;另一种方式是通过robots协议。
robots协议全称为网络爬虫排除协议(robotsexclusionprotocol),又称为爬虫规则,网站通
过robots协议告知爬虫程序哪些页面或内容不能被抓取,哪些页面或内容可以被抓取。
robots协议以文本形式存放于网站根目录下,文件名为robots.txt(注意均为小写字
母)。例如打开浏览器,在地址栏中输入https://www.baidu.com/robots.txt,就可以看到
百度robots协议的全部内容,下面列举一部分robots协议内容讲解百度设置的爬虫规则。 
User-agent: Googlebot 
Disallow: /baidu 
Disallow: /s? 
Disallow: /shifen/ 
Disallow: /homepage/ 
Disallow: /cpro 
Disallow: /ulink? 
Disallow: /link? 
Disallow: /home/news/data/ 
Disallow: /bh 
User-agent: * 
Disallow: / 
User-agent用于描述规则对哪些爬虫有效。第一组规则中的User-agent 是

Googlebot,这是Google网页抓取爬虫,也称为信息采集软件,下面列出的规则是针对
Google爬虫的约束。一般来说,规则由Alow和Disalow开头,Alow指定允许访问的网
页,Disalow指定不允许访问的网页。规则是以正斜线“/开(”) 头的特定的网址或模式,例如, 
Disalow:/baidu表示不允许爬虫访问网站中名为baidu的目录或页面;Disalow:/s? 表
示不允许爬虫访问网站中名为“s?”字符串的目录或页面;Disalow:/shifen/表示不允许爬
虫访问网站中名为“shifen”的目录及其中的一切;其他规则大家可以自行解释。第二组规
则表示所有的爬虫都不能访问的目录(“*”代表所有,“/”代表根目录)。

robot

注意,s协议是国际互联网界通行的道德规范,是一个协议,而不是一个命令,不是

强制执行,所以需要大家自觉遵守。使用爬虫时严格审查所抓取的内容,如发现涉密信息, 

应及时停止并删除,避免触碰法律底线。

在大数据时代,人们释放了许多个人隐私信息,带来了一系列问题,例如信息隐私被肆

意侵害泄露等。2020年7月,张某通过计算机技术手段入侵了某教育网站的数据库,获取

了该网站备份的12万余条学生信息,其中包括学生姓名、性别、户籍、身份证号、学历等内容

的有效信息1.并在某论坛上出售这些学生信息。张某非法获取并利用其他公民

8万余条, 

的相关个人信息,情节尤其严重,构成了非法侵害其他公民的相关个人信息的违法犯罪

行为。

从计算机专业技术人员角度来讲,对于大数据的挖掘应该遵守行为规范,加强行业自

律,符合伦理道德,不要恶意挖掘用户的信息,尊重个人隐私权。

3.网页下载器
3 

网页下载器的主要任务是下载网页内容,那么首先要通过HTTP库发起请求,常用库
是urlib库和Requests库。本书主要介绍Requests库的使用。

3.1 
HTTP的请求信息
3.
爬虫要发起HTTP请求,就必须了解HTTP的请求信息。一般来说,HTTP的请求信
息包括3部分:请求方法、请求头和请求正文。最常用的请求方法是get方法和post方法。
get方法的作用是请求获取指定页面内容;post方法向指定网址提交数据,进行处理请求。
打开一个网站一般使用get方法,如果涉及向网站提交数据,例如登录,就用到了post方
法。请求头包含客户端的一些信息,例如UserAgent和Cookie等,这些信息经常用于爬虫
程序中。

下面打开一个网页,查看浏览器向网站发送的HTTP请求。打开Firefox浏览器或其
他浏览器,在网页空白处右击,在弹出的快捷菜单中选择“检查”,浏览器下方出现子窗口,选
择此窗口左侧的“网络”,打开网络监视器。然后在浏览器的地址栏中输入hs:
iu.om, 单击最上面一个请求, 
tp//www. 

badc可以看到网络监视器中出现很多请求, 右侧出现该请求的

详细信息,如图3-3所示。这就是当前浏览器向百度服务器发起的HTTP请求所包含的

信息。

从图3-3可以看到,这次HTTP请求所用的方法是get方法,请求地址为htps:// 
www.adc状态码为200, 其中这

biu.om, 表明请求已经成功。请求头在消息头窗口的下方, 


5 5 
图3-3 Firefox浏览器的检查工具
次请求的UserAgent信息如下: 
Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:91.0) Gecko/20100101 Firefox/91.0 
这个UserAgent信息表示这次请求的发起者是Firefox浏览器,同时显示了浏览器的
版本信息。下面列举几个常见的UserAgent信息。
(1)Mozilla/5.0 (Windows NT 6.1;Win64;x64;rv:91.0)Gecko/20100101 
Firefox/91.0。
浏览器名称:Firefox。
浏览器版本号:91.0。
操作系统:Windows。
(2)Mozilla/5.0(WindowsNT6.3;WoW64;Trident/7.0;rv:11.0)likeGecko。
浏览器名称:IE。
浏览器版本号:11。
操作系统:Windows。
(3)Mozilla/5.0(WindowsNT10.0;Win64;x64)appleWebkit/537.36(khtml,like 
Gecko)chrome/51.0.2704.79safari/537.36edge/14.14393。
浏览器名称:Edge。
浏览器版本号:14.14393。
操作系统:Windows。
(4)Mozilla/5.0(compatible;Konqueror/3.5;Linux)KHTML/3.5.5(likeGecko) 
(Kubuntu)。
浏览器名称:Konqueror。
浏览器版本号:3.5。

56 
操作系统:Kubuntu。
(5)Mozilla/5.0(X11;Linuxi686)AppleWebKit/535.7(KHTML,like Gecko) 
Ubuntu/11.04Chromium/16.0.912.77Chrome/16.0.912.77Safari/535.7。
浏览器名称:Chromium。
浏览器版本号:16.0.912.77。
操作系统:Ubuntu。
本节介绍了HTTP请求信息的基本内容,以及在浏览器中检查请求信息的方法,编写
爬虫程序时会经常用到这些内容。下面以Requests库为例讲解HTTP请求的实现。
3.3.2 Requests库的安装
Requests库是公认的爬取页面最好的第三方库。它的语法非常简洁,有时可以用一条
语句从网页上获取信息。Requests库使用pip安装,命令如下。 
pip install requests 
如果已经安装了Anaconda,Requests不需要另行安装,Anaconda默认安装时包含这
个库。安
装好Requests库后,在Python开发环境中导入。命令如下。 
import requests 
3.3.3 Requests库的请求和响应
Requests库的常用方法如表3-1所示。如果想获取某网址的资源,可以使用get()和
head()方法:get()方法获得该网址下的全部资源,head()方法获得该网址页面的头部资
源。如果要将资源放到该网址对应的位置上,可以使用post()、put()或patch()方法;如果
想删除该网址对应的资源,可以使用delete()方法。这里只介绍本书中用到的get()和head() 
方法,其他方法读者可查阅相关资料。
表3-1 Requests库的常用方法
方 法说 明
requests.request() 构造一个请求,各方法的基础方法
requests.get() 请求获取目标网页资源
requests.head() 请求获取目标网页资源的头部信息
requests.post() 向目标网页提交POST请求
requests.put() 请求向目标网址存储资源
requests.patch() 请求在目标网址进行局部修改
requests.delete() 请求在目标网址删除资源 
(1)get()方法。Requests库中get()方法的语法格式如下。
扫一扫

5 7 
requests.get(url,params=None,**kwargs) 
其中,参数url表示目标网页的URL链接,params是以字典或字节流的格式作为参数
增加到URL中,kwargs是12个控制访问参数,以键值对的形式表示,比较常用的关键字参
数是headers参数。
① 不带参数的get()方法。代码如下。 
r=requests.get("https://www.baidu.com") 
print(r.status_code) 
输出结果为: 
200 
上面的代码使用get()方法获取百度首页,返回了一个包含百度首页内容的响应对象
(Response对象),并赋值给r。
响应对象的属性包括获取本次请求的响应状态、响应头和响应体等信息。例如,在上面
的代码中,r.status_code表示响应状态,响应状态码是200,表示响应成功,404为找不到页
面,502为服务器错误等。响应对象常用的属性如表3-2所示。
表3-2 响应对象常用的属性
属 性说 明属 性说 明
status_code HTTP请求的响应状态header 响应头信息
text 响应内容的字符串形式content 响应内容的二进制形式
encoding 响应内容的编码方式以及修改编码 
响应对象的text属性可以输出页面内容,encoding属性可以查看或修改页面的编码形
式,例如: 
r=requests.get("https://www.baidu.com") 
print(r.encoding) 
print(r.text) 
输出结果如图3-4的(a)图所示,Requests库使用ISO-8859-1解码百度首页的内容,但
其中有一些乱码,ISO-8859-1不能解码其中的某些内容,修改encoding属性,使r.text正常
输出网页内容。代码如下。 
r.encoding='UTF-8' 
print(r.encoding) 
print(r.text) 
输出结果如图3-4的(b)图所示,这时百度首页内容能够正常输出了,对比两次r.text 
的输出结果,出现乱码的原因是中文在ISO-8859-1编码方式下不能正常编码。

58 
图3-4 Response对象的text属性输出页面内容 
② 带params参数的get()方法。
如果在百度首页搜索“Python”,搜索结果的URL链接为: 
https://www.baidu.com/s?ie=UTF-8&wd=Python 
这个URL中“?”后面有两个参数“ie”和“wd”,分别表示编码形式和搜索关键字。
Requests使用params为URL提供这样的参数。params必须是字典形式,例如在百度首
页搜索“Python”的URL页面的get()方法编写如下。 
mypara={'ie':'UTF-8', 'wd':'Python'} 
r=requests.get("https://www.baidu.com/s", params=mypara) 
print(r.url) 
输出结果为: 
https://www.baidu.com/s?ie=UTF-8&wd=Python 
③ 带headers参数的get()方法。
每一个HTTP请求都包含一个请求头headers,在图3-3中打开百度首页时,本次请求
的头部信息headers中记录了请求发起者是Firefox浏览器。很多网站不想让爬虫爬取网
站内容,消耗服务器资源,所以设计了反对爬虫策略,最常用的反爬策略就是服务器通过读
取headers中的UserAgent值来判断访问请求来自于浏览器或其他地址。人们可以使用
headers参数自定义请求头信息。headers参数是一个字典形式,示例如下。 
r=requests.get("https://www.baidu.com") #不带headers 参数的get()方法
print(r.request.headers) #查看请求头信息
myheader= {' User - Agent ': ' Mozilla/5. 0 (Windows NT 6. 1; Win64; x64; rv: 91. 0) 
Gecko/20100101 Firefox/91.0'} #User Agent 设置为Firefox 浏览器
r=requests.get("https://www.baidu.com", headers=myheader) 
#带headers 参数的get()方法
print(r.request.headers)

5 9 
输出结果为: 
{'User-Agent': 'python- requests/2.23.0', 'Accept- Encoding': 'gzip, deflate', 
'Accept': '*/*', 'Connection': 'keep-alive'} 
{'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:91.0) Gecko/20100101 
Firefox/91. 0 ', ' Accept - Encoding ': ' gzip, deflate ', ' Accept ': ' */* ', 
'Connection': 'keep-alive'} 
可以看出,如果没有修改headers中UserAgent值,UserAgent值是python-requests, 
如果传递了headers参数到Requests请求中,那么请求的头信息被修改为相应值。
(2)head()方法。head()方法主要用于获取HTML网页头信息。例如,抓取百度首页
的头部信息,示例代码如下。 
r=requests.head('https://www.baidu.com') 
print(r.headers) 
输出结果为: 
{' Cache - Control ': ' private, no - cache, no - store, proxy - revalidate, no - 
transform', 'Connection': 'keep-alive', 'Content- Encoding': 'gzip', 'Content- 
Type': 'text/html', 'Date': 'Mon, 19 Sep 2022 09:37:04 GMT', 'Last- Modified': 
'Mon, 13 Jun 2016 02:50:01 GMT', 'Pragma': 'no-cache', 'Server': 'bfe/1.0.8.18'} 
对象r 是一个响应对象,r.headers获取响应头信息,也就是拟抓取网页的头信息。返回结
果是一个字典,其中包括网页的缓存信息、连接方式、内容编码、内容格式、访问日期等信息。
3.4 网页解析器
从目标网址下载了页面内容后,要从HTML源码中提取数据,这就需要对网页进行解
析。常用的网页解析工具有正则表达式、lxml库和BeautifulSoup库。正则表达式采用字
符串匹配的方式查找目标内容,解析速度快,但语法复杂,如果对解析速度要求比较高的爬
虫,可以使用正则表达式。lxml库支持HTML 和XML 的解析,使用XPath(XMLPath 
Language),语法简单、解析效率高,推荐新手入门使用。BeautifulSoup采用Python自带
的html.parse作为解析器,也可以采用lxml作为解析器,语法比较简单,但解析速度慢。本
节主要讲解lxml库以及XPath语法。
3.4.1 lxml库的安装
lxml不是Python的标准库,需要另行安装。使用pip安装命令如下。 
pip install lxml 
或者用wheel文件安装,首先从http://www.lfd.uci.edu/~gohlke/pythonlibs/#lxml 
中下载对应系统版本的wheel文件,然后运行如下安装命令。 
pip install lxml-4.2.1-cp36-cp36m-win_amd64.whl

60 
如果已经安装了Anaconda,lxml不需要另行安装,Anaconda默认安装时包含这个库。
3.4.2 XPath常用语法
XPath是XML的路径语言,可以在XML和HTML文档中查找内容。XPath采用了
树状结构,提供了非常简明的路径选择表达式。lxml支持XPath语言解析HTML文档中
的内容,它的大部分功能包含在lxml.etree下,所以使用lxml时要从lxml导入etree。
XPath常用语法如表3-3所示。
表3-3 XPath常用语法
表 达 式说 明
// 从当前节点选取子孙节点
/ 从当前节点选取下一级子节点
.. 当前节点的父节点
[@attrib='value'] 查找给定属性等于指定值的所有元素
* 通配符,表示任意 
下面以一段HTML源码为例讲解XPath查找并提取文本的语法。 
from lxml import etree 
html_text=''' 
<body> 
<div id='texts'> 
<li class="css1"><a href="text1.html">First text</a></li> 
<li class="css2"><a href="text2.html">Second text </a></li> 
<li class="css3"><a href="text3.html">Third text </a></li> 
</div> 
</body> 
''' 
HTML语言中<body>和</body>之间的内容是网页主体,可以从其中提取所需要
的信息。例如提取“Firsttext”这段文字,首先给定它在该页面的路径,XPath就是通过该路
径找到文字“Firsttext”,具体代码如下。
(1)首先将这段源码送入etree的HTML方法中。代码如下。 
myelement=etree.HTML(html_text) 
print(type(myelement)) 
输出结果为: 
<class 'lxml.etree._Element'> 
得到的myelement实际上是一个Element对象,如果想看到解析的内容,可以使用如
下代码。 
print(etree.tostring(myelement).decode('utf-8')) 
扫一扫

6 1 
输出结果为: 
<class 'lxml.etree._Element'> 
<html><body> 
<div id="texts"> 
<li class="css1"><a href="text1.html">First text</a></li> 
<li class="css2"><a href="text2.html">Second text </a></li> 
<li class="css3"><a href="text3.html">Third text </a></li> 
</div> 
</body> 
</html> 
(2)查找“li”标签。Element对象myelement的XPath方法可以查找指定路径,“//”表
示从根节点开始查找。 
li_text=myelement.xpath('//div/li') 
print(li_text) 
输出结果为: 
<Element li at 0x791c648>, <Element li at 0x950bcc8>, <Element li at 0x950b448>] 
运行结果是包含3个Element元素的列表,这是因为HTML源码中有3个“li”标签。
要查找的内容“Firsttext”在第一个“li”标签下,可以使用li[1]表示,注意XPath语法中序号
从1开始。
(3)提取文本内容。HTML源码中“Firsttext”是第一个“li”标签下“a”标签里的文本
内容,可以使用text()获取标签的文本内容。 
text=myelement.xpath('//div/li[1]/a/text()') 
print(text) 
输出结果为: 
['First text'] 
输出结果是包含查找文本内容的列表,使用text[0]即可得到“Firsttext”字符串。
(4)提取属性值。如果要提取“Firsttext”对应的链接地址“text1.html”,因为链接地址
“text1.html”是“a”标签href属性的值,可以使用@href属性获取该链接地址。 
link_text=myelement.xpath('//div/li[1]/a/@ href') 
print(link_text) 
输出结果为: 
['text1.html'] 
另外,使用[@attrib='value']可以查找给定属性等于指定值的所有元素,例如第一个
“li”标签的路径可以用li[@class="css1"],功能等价于li[1]。使用属性值查找标签比用序

62 
号查找更方便些,只要知道标签的属性值,而不用查看这是第几个标签。使用属性值查找方
式提取标签文本内容“Firsttext”和链接地址“text1.html”的代码如下。 
text=myelement.xpath('//div/li[@class="css1"]/a/text()')[0] 
print(text) 
link_text=myelement.xpath('//div/li[@class="css1"]/a/@href')[0] 
print(link_text) 
输出结果为: 
First text 
text1.html 
3.4.3 lxml库应用实例
下面以百度首页为例介绍使用lxml库对真实网页源码的解析过程。假设要提取百度
首页中的“新闻”一词和它对应的链接地址,lxml库的使用过程如下。
首先需要了解“新闻”所在的路径。使用Firefox浏览器打开百度首页,在页面空白处
右击,在弹出的快捷菜单中选择“检查”功能,打开检查窗口,最左端有一个箭头形状的按钮, 
使用它可以选取页面上的元素,单击箭头按钮,在百度首页上单击“新闻”,这时“查看器”子
窗口中就会显示“新闻”在HTML源码中的位置,如图3-5所示。要想得到“新闻”一词的路
径,浏览器提供了一种简便方法:在“查看器”子窗口中突出显示的“新闻”的HTML代码上
右击,在弹出的快捷菜单中选择“复制”→“XPath”,即可得到“新闻”所对应的XPath语法的
路径:/html/body/div[1]/div[1]/div[3]/a[1]。这里使用了“div”标签的序号形式来表示
路径,即“新闻”一词在div[1]→div[1]→div[3]目录下。
图3-5 百度首页的HTML源码突出显示“新闻”一词
扫一扫

6 3 
使用Firefox浏览器得到的XPath语法路径后,提取百度首页上“新闻”一词和它对应
的链接地址代码如下。 
myheader= {' User - Agent ': ' Mozilla/5. 0 (Windows NT 6. 1; Win64; x64; rv: 91. 0) 
Gecko/20100101 Firefox/91.0'} 
r=requests.get("https://www.baidu.com", headers=myheader) 
myelement=etree.HTML(r.text) 
news_link=myelement.xpath('//html/body/div[1]/div[1]/div[3]/a[1]/@href')[0] 
news_text=myelement.xpath('//html/body/div[1]/div[1]/div[3]/a[1]/text()')[0] 
print(news_link) 
print(news_text) 
输出结果为: 
http://news.baidu.com 
新闻
在上面的代码中,XPath语法的路径使用了“div”标签的序号形式,也可以使用“div”标
签的id 属性值为“s-top-left”查找“新闻”一词所在的路径,例如语句news_text= 
myelement.xpath(/' /div[@id="s-top-left"]/a[1]/text()')[0]的返回值为“新闻”。
值得注意的是,在上面的代码中,发起Requests请求时传递了headers参数,将User- 
Agent定义为Firefox浏览器。如果不设置User-Agent,“新闻”所在的“div”标签的id属性
值为“u1”,这时代码需要修改为: 
r=requests.get("https://www.baidu.com") 
r.encoding='UTF-8' #解析内容能够正常显示汉字
myelement=etree.HTML(r.text) 
news_link=myelement.xpath('//div[@id="u1"]/a[1]/@href')[0] 
news_text=myelement.xpath('//div[@id="u1"]/a[1]/text()')[0] 
print(news_link) 
print(news_text) 
3.5 案例实现
下面以1.5节中的综合实战项目为例,从“北京链家网”爬取包括城区名(district)、街道
名(street)、小区名(community)、楼层信息(floor)、有无电梯(lift)、面积(area)、房屋朝向
(toward)、户型(model)和租金(rent)等租房信息。
爬取数据的程序流程图如图3-6所示。
(1)导入库。代码如下。首先导入爬取过程中所需的库,这里仅对库进行简单说明,后
面会详细讲解。 
import csv 
import random 
import time 
import requests 
import pandas as pd 
from lxml import etree 
扫一扫

64 
图3-6 “北京链家网”租房数据爬取流程图
其中,requests库用于请求指定页面并获取响应,etree库对返回的页面进行XPath解
析,以获取指定的数据,random 库和time库用于爬取过程中的一些设置,Pandas库和csv 
库用于处理和保存文件。
(2)输入“北京链家网”的城市简称和要爬取的房源页面的页码范围。打开租房页面, 
首先展现的是房源列表页,通过观察不同城市的链家网租房页面的URL链接可知,一个房
源列表页的URL链接主要分为3部分:城市的拼音简写、页码和其他相同部分,例如下面
给出三个房源列表页的URL。 
#北京
https://bj.lianjia.com/zufang/pg4/#contentList 
#重庆
https://cq.lianjia.com/zufang/pg5/#contentList 
#上海
https://sh.lianjia.com/zufang/pg6/#contentList 
其中,第一条URL中的“bj”表示城市北京的拼音简写,“pg4”表示第4页。通过设置房
源列表页URL的城市拼音简写,除了可以爬取北京的租房数据外,也可以爬取其他城市的

6 5 
租房数据。
本案例使用城市(如bj)和页码(pg4)的拼音简称构造一条房源列表页的URL链接,其
中“#contentList”用于页内定位,可以忽略。
(3)爬取并解析房源列表页。由于本案例拟爬取的房源信息在房源列表页和每一个房
源的详情页均有分布,因此首先需要获取每一个房源详情页的URL链接。另外,房源列表
页中还包含每一个房源所在的地理位置(所在城区、街道和小区)。下面介绍如何对房源列
表页的页面信息进行分析,以获得房源详情页的URL链接和每一个房源的地理位置。
使用火狐浏览器打开“北京链家网”的租房页面https://bj.lianjia.com/zufang/,在页面
空白位置右击,在弹出的快捷菜单中选择“检查”功能,单击页面最左侧小箭头形状的按钮, 
随后指向其中的一个房源信息,在“查看器”子窗口中右击蓝色文字的MTML 代码,选择
“复制”→“整体HTML”,得到如下的HTML内容(为简化分析难度,仅保留部分代码)。 
<div class="content__list"> 
<div class="content__list--item"> 
<div class="content__list--item--main"> 
<p class="content__list--item--title"> 
<a class="twoline" target="_blank" 
href="/zufang/BJ2840486736310837248.html"> 
整租·长阳国际城二区3 室1 厅南/北</a> 
</p> 
<p class="content__list--item--des"> 
<a target="_blank" href="/zufang/fangshan/">房山</a>-<a 
href="/zufang/changyang1/" target="_blank">长阳</a>-<a 
title="长阳国际城二区" href="/zufang/c1111053458322/" 
target="_blank">长阳国际城二区</a> 
<i>/</i> 
89.00m2 
<i>/</i>南北<i>/</i> 
3 室1 厅1 卫<span class="hide"> 
<i>/</i> 
中楼层 (20 层) 
</span> 
</p> 
</div> 
</div> 
</div> 
通过观察会发现,房源详情页的URL链接包含在<divclass="content__list--item"> 
标签中的<aclass="twoline">标签的href属性中,房源的地理位置(城区名、街道名和小
区名)包含在<pclass="content__list--item--des">标签中的3个“a”标签内。
获得房源的URL链接和地理位置的具体过程如下。
① 首先,通过XPath获取房源的URL 链接,路径为//a[@class="content__list-- 
item--aside"]/@href。使用该路径可以获取当前页面中所有class属性为“content__list-- 
item--aside”的“a”标签的href属性值,得到的结果为当前页面所有房源的URL链接列表
detailsUrl。
扫一扫

66 
② 然后,通过XPath获取房源的地理位置(城区名、街道名和小区名),路径为//p[@ 
class="content__list--item--des"]/a/text()。使用该路径可以获取当前页面中所有class 
属性为“content__list--item--des”的“p”标签下“a”标签的文本内容,得到的结果为当前页面
所有房源的地理位置列表location。需要注意的是,每一个“p”标签下共有3个“a”标签,分
别对应房源的城区名、街道名和小区名。
③ 最后,通过遍历列表detailsUrl和列表location,将房源详情页URL链接和对应的
城区名、街道名和小区名存放在字典中。
该部分代码如下。 
def getPageLines(city, page): 
""" 
获取指定城市和页码所在页面中的房源URL 链接、所在地理位置(district street 
community),并分别存入house 字典中 
:param city:城市简称 
:param page:要爬取的页码 
:return:字典列表 
""" 
#构造房源列表页的URL 链接 
URL="https://" + city + ".lianjia.com/zufang/pg"+ str(page) 
#构造房源详情页URL 链接的公共部分 
baseUrl=URL.split("/")[0]+ "//" + URL.split("/")[2] 
#爬取房源列表页,并处理响应信息 
response=requests.get(url=URL) 
#获取页面HTML,并对其进行解析 
html=response.text 
myelement=etree.HTML(html) 
#提取本页面所有房源的URL 链接和地理位置 
detailsUrl=myelement.xpath('//a[@ class="content__list- - item- - aside"]/@ 
href') 
location=myelement.xpath('//p[@class="content__list- - item- - des"]/a/text 
()') 
#将数据存入字典列表中 
houses=list() 
for i in range(len(detailsUrl)): 
#获取房源详情页的URL 链接 
detailsLink=baseUrl + detailsUrl[i] 
#获取房源所在地理位置 
lineIndex=i * 3 
district=location[lineIndex] 
street=location[lineIndex + 1] 
community=location[lineIndex + 2] 
#将房源详情页URL 链接和所在地理位置存入字典 
house={} 
house["detailsLink"]=detailsLink 
house["district"]=district 
house["street"]=street 
house["community"]=community 
houses.append(house) 
return houses

6 7 
(4)爬取并解析房源详情页。从房源详情页中可以获取房屋楼层、电梯、面积、朝向、户
型和租金等信息。在浏览器中打开一个房源详情页,通过火狐浏览器的“检查”功能获取房
源详情页面的部分HTML源码。房源详情页的部分HTML源码如下。 
<div class="content__aside--title"> 
<span>5500</span>元/月 
(季付价) 
<div class="operate-box">……</div> 
</div> 
<ul class="content__aside__list"> 
<li><span class="label">租赁方式: </span>整租</li> 
<li><span class="label">房屋类型: </span>3 室1 厅1 卫89.00m2 精装修</li> 
<li class="floor"> 
<span class="label">朝向楼层: </span> 
<span class="">南/北中楼层/20 层</span> 
</li> 
<li> 
<span class="label">风险提示: </span> 
<a href="https://m.lianjia.com/text/disclaimer">用户风险提示</a> 
</li> 
</ul> 
<div class="content__article__info" id="info"> 
<h3 id="info">房屋信息</h3> 
<ul> 
<li class="fl oneline">基本信息</li> 
<li class="fl oneline">面积: 89.00m2</li> 
<li class="fl oneline">朝向: 南北</li> 
<li class="fl oneline">&nbsp;</li> 
<li class="fl oneline">维护: 7 天前</li> 
<li class="fl oneline">入住: 随时入住</li> 
<li class="fl oneline">&nbsp;</li> 
<li class="fl oneline">楼层: 中楼层/20 层</li> 
<li class="fl oneline">电梯: 有</li> 
… 
</ul> 
… 
</div> 
通过分析该段HTML源码可知,房屋租金位于class属性为“content__aside--title”的
<div>标签下的<span>标签中,房屋户型位于class属性为“content__aside__list”的
<div>标签下的第二个“li”标签中,楼层、电梯、面积和朝向4个房屋信息都存在于class属
性为“content__article__info”的<div>标签下的“ul”标签下的“li”标签中。通过XPath获
取“li”标签中的内容,并对文本内容进行数据清洗,以得到字典类型的目标数据,最后将其
存入对应的house字典。
获得房源的楼层、电梯、面积、朝向、户型和租金等信息的具体过程如下。
① 在上一步得到的house字典中读取房源详情页的URL链接,发送请求获取房源详
情页面的HTML源码,并对其进行解析。
② 通过XPath获取房屋租金rent和房屋户型model,得到的结果存入house字典。
③ 通过XPath获取房屋楼层、电梯、面积和朝向。因为HTML内容中解析得到的数据
扫一扫

68 
含有空格,使用dataCleaning()方法对其进行数据清洗,该方法同时对数据进行类型转换, 
最终将得到的房屋楼层、电梯、面积和朝向数据存入house字典。
至此,所需的数据都保存在house字典中,该部分代码如下。 
def getDetail(house): 
""" 
爬取并解析房源详情页的数据,将其存入字典中。 
:param house: 含有房源详情页URL 链接和地理位置的字典 
:return: 含有案例拟爬取数据的字典 
""" 
#读取房源详情页的URL 链接 
url=house["detailsLink"] 
try: 
response=requests.get(url=url, timeout=10) 
except: 
return None,None 
#获取房源详情页面HTML,并对其进行解析 
myelement=etree.HTML(response.text) 
#获取房屋租金和房屋户型 
house["rent"]= myelement.xpath('//div[@ class="content__aside- - title"]/ 
span/text()')[0] 
house["model"]=(myelement.xpath('//ul[@ class="content__aside__list"]/li 
[2]/text()'))[0].split(" ")[0] 
#获取房屋其他信息:楼层, 电梯, 面积, 朝向 
details=myelement.xpath('//div[@class="content__article__info"]/ul[1]/li/ 
text()') 
details=dataCleaning(details) 
house["floor"]=details["楼层"] 
house["lift"]=details["电梯"] 
house["area"]=details["面积"] 
house["toward"]=details["朝向"] 
return house,response.status_code 
在上面的代码中,使用dataCleaning()函数对一条房屋的信息进行数据清洗,以获取房
屋的楼层、电梯、面积和朝向等数据。数据清洗包括两部分:删除数据中的空格'\xa0';将数
据由列表类型转换成字典类型,以方便存储。例如,一条房屋信息原本的类型是列表类型: 
['基本信息','面积:89.00㎡','朝向:南',\' xa0','维护:5天前','入住:随时入住',\' xa0','楼层: 
中楼层/28层','电梯:有','\xa0','车位:暂无数据','用水:暂无数据','\xa0','用电:暂无数
据','燃气:有',\' xa0','采暖:集中供暖'],经过dataCleaning()函数后,该房屋类型被转换为
字典类型:{'面积':8' 9.00㎡','朝向':'南','维护':5' 天前','入住':'随时入住','楼层':'中楼层/ 
28层','电梯':'有','车位':'暂无数据','用水':'暂无数据','用电':'暂无数据','燃气':'有','采
暖':'集中供暖' }。dataCleaning()函数的具体实现如下。 
def dataCleaning(details): 
""" 
对房屋信息进行清洗 
:param details: 列表类型,一条房屋信息 
:return: 字典类型,清洗后的房屋信息 
"""[1:]

6 9 
details=details 
new_details=list() 
for detail in details: 
if detail== "\xa0": 
continue 
detail=str(detail).split(': ') 
new_details.append(detail) 
return dict(new_details) 
爬取网页时,不可避免会遇到“\xa0”字符串。“\xa0”其实表示空格。“\xa0”属于
latin1(ISO/IEC_8859-1)中的扩展字符集字符,代表空白符nbsp(non-breakingspace)。
latin1字符集可向下兼容ASCII码。
(5)保存数据。通过Python的内置库CSV 实现对字典数据按行保存。该部分代码
如下。 
def save(row, fileName): 
""" 
按行保存数据 
:param row: 字典类型,每一行的数据 
:param fileName: 数据保存的文件名 
""" 
with open(fileName, "a+", newline='', encoding='gbk') as f: 
writer=csv.DictWriter(f, fieldnames=fieldnames) 
writer.writerow(row) 
(6)主程序。在主函数中,从键盘输入链家网的城市简称和爬取的页码范围,并新建
CSV 文件,以保存爬取的数据。然后调用上述步骤中的各个函数进行数据爬取。
所有数据爬取完成后,通过第三方库Pandas对数据去除重复行,并在数据中添加一列
“ID”作为索引列。至此,爬取任务完成。
该部分代码如下。 
if __name__ == '__main__': 
city = input("请输入要爬取的城市拼音(如北京: bj,上海: sh): ").strip().lower() 
pageRange=input("请输入要爬取的页码范围(如第1 页到第100 页: 1-100): ").strip() 
startPage=int(pageRange.split("-")[0]) 
endPage=int(pageRange.split("-")[1]) + 1 
#将爬取的数据保存在CSV 表 
fileName=city + "_lianJia.csv" 
fieldnames=['floor', 'lift', 'district', 'street', 'community', 'area', 
'toward', 'model', 'rent'] 
with open(fileName, "w", newline='') as f: 
#将表头写入CSV 表 
writer=csv.DictWriter(f, fieldnames=fieldnames) 
writer.writeheader() 
startTime=time.time() 
for page in range(startPage, endPage): 
print("\n--->>正在爬取第" + str(page) + "页") 
houses=getPageLines(city=city, page=page) 
for house in houses: 
扫一扫