网络爬虫的实现技术主要有PHP、Go、C++、Java、Python。
PHP 语言的优点是语法简洁,容易上手,拥有丰富的网络爬虫功能模块;缺点是对多线程的支持不太友好,需要借助扩展实现多线程技术,并发处理的能力相对较弱。
Go语言的优点是高并发能力强、开发效率高、丰富的标准库通过Go语言开发的网络爬虫程序性能更好,缺点是普及性不高。
C++ 语言的优点是运行速度快、性能强;缺点是学习成本高代码成型速度慢。
Java在网络爬虫方向已经形成了完善的生态圈,非常适合开发大型网络爬虫项目,但重构成本比较高。
使用 Python在网络爬虫方向已经形成完善的生态圈,它拥有娇俏的多线程处理能力,但是网页解析能力不够强大。
Python 网络爬虫的流程:1.抓取网页数据2.解析网页数据3.存储数据
urllib库
快速使用urllib爬取网页
用urllib库爬取网页数据
urllib 库是 Python 内置的 HTTP 请求库它可以看做是处理URL的组件集合。
模块名称 | 描述 |
urllib. request | 请求模块 |
urllib. error | 异常处理模块 |
urllib. parse | url 解析模块 |
urllib. robotparser | robots.txt 解析模块 |
例子:
import urllib.request # 调用 urllib.request 库的 urlopen 方法,并传入一个 url response =urllib.request.urlopen( 'http://www.baidu.com') #使用read方法读取获取到的网页内容 html = response.read().decode('UTF-8') #打印网页内容 print(html)
Python2中使用的是urllib2库来下载网页,该库的用法如下所示:
import urllib2
response=urllib2.urlopen('http://www.baidu.com')
Python3出现后,之前 Python2中的 urllib2 库被移到了 urllib.request模块中,之前 urllib2 中很多函数的路径也发生了变化,希望大家在使用的时候多加注意。
分析 urlopen 方法
前面在爬取网页时,有一句核心的爬虫代码:
response = urllib.request.urlopen('http://www.baidu.com')
上述代码调用的是urllib.request 模块中的 urlopen 方法,它传入了个百度首页的URL,使用的协议是HTTP,这是 urlopen 方法最简单的用法。
urlopen 方法可以接收多个参数,定义格式如下
urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None,capath=None, cadefault=False, context=None)
参数如下:
url-- 表示目标资源在网站中的位置。
data-- 用来指明向服务器发送请求的额外信息。
vtimeout一该参数用于设置超时时间,单位是秒。
context-实现SSL加密传输,该参数很少使用。
使用 HTTPResponse 对象
使用urlopen 方法发送HTTP 请求后,服务器返回的响应内容封装在一个HTTPResponse类型的对象中。
例如:
import urllib.request
response = urllib.request.urlopen(http://www.itcast.cn')
print(type(response))#<class
#'http.client.HTTPResponse'>
HTTPResponse 类属于 http.client 模块该 类提供了获取 URL、状态码、响应内容等一系列方法。
- geturl0-- 用于获取响应内容的 URL,该方法可以验证发送的HTTP请求是否被重新调配。
- info0-- 返回页面的元信息。
- getcode0-- 返回 HTTP 请求的响应状态码。
例子:
import urllib.request
response = urllib.request.urlopen('http://python.org')
#获取响应信息对应的URL
print(response.geturl())
#获取响应码
print(response.getcode())
#获取页面的元信息
print(response.info())
运行结果:
构造 Request 对象
如果希望对请求执行复杂操作,则需要创建一个Request 对象来作为 urlopen 方法的参数。
#将url作为 Reque st方法的参数,构造并返回一个Reque st 对象
request = urllib.request.Request(http://www.baidu.com')
#将Request对象作为 urlopen 方法的参数,发送给服务器并接收响应
response = urllib.request.urlopen(request)
在使用urllib 库发送URL的时候,我们推荐大家使用构造 Request对象的方式。
在构建请求时,除了必须设置的 url 参数外,还可以加入很多内容,例如下面的参数参数如下:
data-- 默认为空,该参数表示提交表单数据,同时HTTP请求方法将从默认的GET方式改为POST方式。
headers-- 默认为空,该参数是一个字典类型,包含了需要发送的HTTP报头的键值对。
使用urllib实现数据传输
URL 编码转换
当传递的URL中包含中文或者其它特殊字符如空格等)时,需要使用 urllib.parse 库中的urlencode方法将URL进行编码。
解码使用的是url.parse 库的 unquote 方法。
import urllib.parse
result =urlib.parse.unquote('a=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2')
print(result)#a=传智播客
处理 GET 请求
GET请求一般用于向服务器获取数据比如说,我们用百度搜索传智播客。
此时,如果使用 Fiddler 查看 HTTP 请求发现有个GET请求的格式如下:
https://www.baidu.com/s?wd=%E4%BC%A0%E6%99%BA%E6%92%AD%E5%AE%A2
在这个请求中,“?”后面的字符串就包含了我们要查询的关键字“传智播客”。
处理 POST 请求
当访问有道词典翻译网站进行词语翻译时,会发现不管输入什么内容,其URL一直都是不变的。
通过使用Fiddler观察,发现该网站向服务器 发送的是POST请求。
当使用有道词典翻译“Python”时,返回 的结果是一个JSON字符串。
添加特定Headers—请求伪装
如果不是从浏览器发出的请求,我们是不能获得 响应内容的。针对这种情况,我们需要将爬虫程 序发出的请求伪装成一个从浏览器发出的请求。
伪装浏览器需要自定义请求报头,也就是在 发送Request请求时,加入特定的Headers。
user_agent = ("User-Agent" : "Mozilla/5.0 (compatible;
MSIE 9.0; Windows NT6.1; Trident/5.0)"}
request = urllib.request.Request(url, headers = user_agent)
request.add_header("Connection", "keep-alive")
代理服务器
简单的自定义opener
很多网站会检测某一段时间某个IP 的访问次数,如果同一IP访问过于频繁,那么该网站会禁止来自该IP的访问。碰到这种情况要怎么办呢?
我们可以使用代理服务器,每隔一段时间换一个 代理。如果某个IP被禁止,那么就可以换成其他 IP继续爬取数据,从而可以有效解决被网站禁止 访问的情况。
opener 是urllib.request.OpenerDirector类的对象,我们之前一直都在使用的urlopen, 就是模块帮我们构建好的一个opener。
但是,urlopen不支持代理、Cookie等其他的 HTTP/HTTPS高级功能,所以如果要想设置代理,不能使用自带的urlopen,而是要自定义opener。
自定义opener需要执行下列三个步骤:
- 使用相关的Handler处理器创建特定功能的处理器对象。
- 通过urllib.request.build_ opener()方法使用这些处理器对象创 建自定义的opener 对象。
- 使用自定义的opener对象,调用open方法发送 请求。
如果程序里所有的请求都使用自定义的opener,可以使用urllib2.install_opener() 将自定义的opener对象定义为全局opener,表示之后凡是调用urlopen,都将使用自定义的opener。
设置代理服务器
我们可以使用urllib.request中的ProxyHandler 方法来设置代理服务器。
httpproxy_handler =urllib.request.ProxyHandler(
{"http" : "124.88.67.81:80"})
opener =urllib.request.build_opener(httpproxy_handler)
免费开放代理的获取基本没有成本我们可以在些代理网站上收集这些免费代理,测试后如果可以用,就把它收集起来用在爬虫上面。免费代理网站主要有以下几个:
- 西刺免费代理IP
- Proxy360代理
- 快代理免费代理
- 全网代理IP
如果代理IP足够多,就可以像随机获取User-Agent 样,随机选择一个代理去访问网站。
import random
proxy_list =
[{"http" : "124.88.67.81:80"}, {http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}, {"http" : "124.88.67.81:80"}]
#随机选择一个代理
proxy = random.choice(proxy_list)
(免费开放代理一般会有很多人都在使用,而且代理有寿命短,速度慢,匿名度不高等缺 点。所以,专业爬虫工程师或爬虫公司会使 用高品质的私密代理,这些代理通常需要找 专门的代理供应商购买,再通过用户名/密 码授权使用。)
超时设置
假设我们要爬取1000个网站,如果其中有100个网站需要等待30s才能返回数据,则要返回所有的数据,至少需要等待3000秒。
我们可以为HTTP请求设置超时时间,一旦超过这个时 间,服务器还没有返回响应内容,那么就会抛出一个 超时异常,这个异常需要使用try语句来捕获。
try:
url = 'http://218.56.132.157:8080',
# timeout设置超时的时间
file = urllib.request.urlopen(url, timeout=1) result = file.read()
print(result)
except Exception as error:
print(error)#<urlopen error timed out>
抓取静态网页数据
requests库
为帮助开发人员抓取静态网页数据,减少开发人员的开发时间,Python提供了一些功能齐全的库,包括urllib、urllib3和Requests,其中urllib是Python内置库,无须安装便可以直接在程序中使用;urllib3和Requests都是第三方库,需要另行安装后才可以在程序中使用。
urllib:urllib是Python最早内置的HTTP客户端库,它涵盖了基础的网络请求功能。
urllib3:urllib3是一个强大的、用户友好的Python的HTTP客户端库,包括线程安全、连接池、客户端 TLS/SSL 验证、压缩编码等功能。
Requests:Requests基于urllib3编写,该库会在请求完后重复使用Socket套接字,不会与服务器断开连接,而urllib库会在请求完之后与服务器断开连接。
Requests库的安装命令如下:
pip install requests
发送基本请求
在Requests库中,GET请求通过调用get()函数发送,该函数会根据传入的URL构建一个请求(每个请求都是Request类的对象),将该请求发送给服务器。get()函数的声明如下:
get(url, params=None, headers=None, cookies=None, verify=True, proxies=None, timeout=None, **kwargs)
- url:必选参数,表示请求的URL。
- params:可选参数,表示请求的查询字符串。
- headers:可选参数,表示请求的请求头,该参数只支持字典类型的值。
- cookies:可选参数,表示请求的Cookie信息,该参数支持字典或CookieJar类对象。
- verify:可选参数,表示是否启用SSL证书,默认值为True。
- proxies:可选参数,用于设置代理服务器,该参数只支持字典类型的值。
- timeout:可选参数,表示请求网页时设定的超时时长,以秒为单位。
使用get()函数可以发送不携带url参数和携带url参数的GET请求。
不携带url参数的GET请求:
若GET请求的URL中不携带参数,我们在调用get()函数发送GET请求时只需要给url参数传入指定的URL即可。
import requests
# 准备URL
base_url = 'https://www.baidu.com/'
# 根据URL构造请求,发送GET请求,接收服务器返回的响应信息
response = requests.get(url=base_url)
# 查看响应码
print(response.status_code)
携带url参数的GET请求
若GET请求的URL中携带参数,在调用get()函数发送GET请求时只需要给url参数传入指定的URL即可。(有两种方式)
①
import requests
base_url = 'https://www.baidu.com/s'
param = 'wd=python'
full_url = base_url + '?' + param # 拼接完整的URL
# 根据URL构造请求,发送GET请求,接收服务器返回的响应信息
response = requests.get(full_url)
# 查看响应码
print(response.status_code)
②
import requests
base_url = 'https://www.baidu.com/s'
wd_params = {'wd': 'python'}
# 根据URL构造请求,发送GET请求,接收服务器返回的响应
response = requests.get(base_url, params=wd_params)
# 查看响应码
print(response.status_code)
发送POST请求
如果网页上form表单的method属性的值设为POST,那么当用户提交表单时,浏览器将使用POST方法提交表单内容,并将各个表单元素及数据作为HTTP请求信息中的请求数据发送给服务器。
例如,登录美多商城时发送的请求是POST请求。此时,我们使用Fiddler工具捕获该请求后,可以看到发送该请求时的请求数据。
在Requests中,POST请求可以通过调用post()函数发送,post()函数会根据传入的URL构建一个请求,将该请求发送给服务器,并接收服务器成功响应后返回的响应信息。post()函数的声明如下:
post(url, data=None, headers=None, cookies=None, verify=True, proxies=None, timeout=None, json=None, **kwargs)
data:可选参数,表示请求数据。该参数可以接收3种类型的值,分别为字典、字节序列和文件对象。当参数值是一个字典时,字典的键为请求数据的字段,字典的值为请求数据中该字段对应的值,例如{"ie": "utf-8","wd": "python"}。
json:可选参数,表示请求数据中的JSON数据。
以美多商城网站为例,为大家演示如何使用post()函数请求美多商城网站首页,具体代码如下。
import requests
base_url = 'http://mp-meiduo-python.itheima.net/login/'
# 准备请求数据
form_data = {
'csrfmiddlewaretoken':'FDb8DNVnlcFGsjIONtwiQoi6PtmCLeBsRgyjx2o2nsZ4MXDEGDeM2dUImEkj9O7t',
'username': 'admin',
'pwd': 'admin',
'remembered': 'on'}
response = requests.post(base_url, data=form_data) # 根据URL构造请求,发送POST请求
print(response.status_code) # 查看响应信息的状态码
处理响应
在Requests库中,Response类的对象中封装了服务器返回的响应信息,包括响应头和响应内容等。除了前面介绍的status_code属性之外,Response类中还提供了一些其他属性。
属性 | 说明 |
status_code | 获取服务器返回的状态码 |
text | 获取字符串形式的响应内容 |
content | 获取二进制形式的响应内容 |
url | 获取响应的最终URL |
request | 获取请求方式 |
headers | 获取响应头 |
encoding | 设置或获取响应内容的编码格式,与text属性搭配使用 |
cookies | 获取服务器返回的Cookie |
获取网页源代码
通过访问Response类对象的text属性可以获取字符串形式的网页源代码。
import requests
base_url = 'https://www.baidu.com/'
# 根据URL构造请求,发送GET请求,接收服务器返回的响应信息
response = requests.get(url=base_url)
# 查看响应内容
print(response.text)
为了保证获取的源代码中能够正常显示中文,这里需要通过Response对象的encoding属性将编码格式设置为UTF-8。
import requests
base_url = 'https://www.baidu.com/'
# 根据URL构造请求,发送GET请求,接收服务器返回的响应信息
response = requests.get(url=base_url)
# 设置响应内容的编码格式
response.encoding = 'utf-8'
# 查看响应内容
print(response.text)
百度首页上除了文字信息之外,还包含一个百度Logo图片。若希望获取百度Logo的图片,我们需要先根据该图片对应的请求URL发送请求,再使用content属性获取该图片对应的二进制数据,并将数据写入到本地文件中。
import requests
base_url = 'https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png'
response = requests.get(base_url)
# 获取百度Logo图片对应的二进制数据
print(response.content)
# 将二进制数据写入程序所在目录下的baidu_logo.png文件中
with open('baidu_logo.png', 'wb') as file:
file.write(response.content)
处理复杂请求
定制请求头
定制请求头分为两步,分别是查看请求头和设置请求头。
打开Fiddler工具,在Chrome浏览器中加载知乎网登录页面,加载完成后切换至Fiddler工具,在窗口左侧选中刚刚发送的HTTP请求,并在窗口右侧查看该请求对应的请求头信息。
在requests中,设置请求头的方式非常简单,只需要在调用请求函数时为headers参数传入定制好的请求头即可,一般是将请求头中的字段与值分别作为字典的键与值,以字典的形式传给headers参数。
import requests
base_url = 'https://www.zhihu.com/signin'
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64'
'AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36'}
# 根据URL和请求头构造请求,发送GET请求,接收服务器返回的响应
response = requests.get(base_url, headers=header)
response.encoding = 'utf-8'
# 查看响应内容
print(response.text)
验证Cookie
以登录后的百度首页为例,使用两种方式演示如何使用Requests实现Cookie登录。
①
import requests
headers = {
'Cookie': '此处填写登录百度网站后查看的Cookie信息', # 设置字段Cookie
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4)'
'AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/53.0.2785.116 Safari/537.36',} # 设置字段User-Agent
response = requests.get('https://www.baidu.com/', headers=headers)
print(response.text)
②
import requests
header = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) '
'AppleWebKit/537.36 (KHTML, like Gecko)Chrome/53.0.2785.116 Safari/537.36'}
cookie = '此处填写登录百度网站后查看的Cookie信息‘# 准备Cookie
jar_obj = requests.cookies.RequestsCookieJar() # 创建RequestsCookieJar类对象
for temp in cookie.split(';') :# 以逗号为分隔符分割cookie,并将获得的键和值保存至jar_obj中
key, value = temp.split('=', 1)
jar_obj.set(key, value)
response = requests.get('https://www.baidu.com/',
headers=header, cookies=jar_obj)
print(response.text)
保持会话
在Requests中,Session类负责管理会话,通过Session类的对象不仅可以实现在同一会话内发送多次请求的功能,还可以在跨请求时保持Cookie信息。 使用Session类的对象先在请求一个测试网站时设置Cookie信息,然后在请求另一个网站时获取Cookie信息,具体代码如下。
import requests
# 创建会话
sess_obj = requests.Session()
sess_obj.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
response = sess_obj.get("http://httpbin.org/cookies")
print(response.text)
在上页代码中,首先创建了一个Session类对象sess_obj,然后基于sess_obj对象发送了一个GET请求到测试网站,并且在请求该测试网站时设置了Cookie信息,其中Cookie的名称设置为sessioncookie,内容为123456789,最后基于sess_obj对象请求另一个网站,获取上次请求时设置的Cookie信息。运行结果如下:
若不使用Session类对象请求测试网站,而是直接使用Requests库请求测试网站。
import requests
requests.get('http://httpbin.org/cookies/set/sessioncookie/123456789')
response = requests.get("http://httpbin.org/cookies")
print(response.text)
运行结果:
SSL证书验证
当使用Requests调用请求函数发送请求时,由于请求函数的verify参数的默认值为True,所以每次请求网站时默认都会进行SSL证书的验证。不过,有些网站可能没有购买SSL证书,或者SSL证书失效,当程序访问这类网站时会因为找不到SSL证书而抛出SSLError异常。 例如,使用Requests请求国家数据网站,具体代码如下。
import requests
base_url = 'https://data.stats.gov.cn/'
header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64 '
'AppleWebKit/537.36 (KHTML, like Gecko)'
'Chrome/90.0.4430.212 Safari/537.36'}
response = requests.get(base_url, headers=header)
print(response.status_code)
运行上页代码,程序抛出SSLError异常,具体如下所示。
requests.exceptions.SSLError: HTTPSConnectionPool(host='data.stats.gov.cn', port=443): Max retries exceeded with url: / (Caused by SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: self signed certificate in certificate chain (_ssl.c:1002)')))
这时需要主动关闭SSL验证,即在调用get()函数时将verify参数设置为False,代码如下所示。
response = requests.get(base_url, headers=header, verify=False)
再次运行代码,控制台没有输出SSLError异常,而是输出了如下警告信息:
C:\Users\admin\AppData\Roaming\Python\Python38\site-packages\urllib3\ connectionpool.py:981: InsecureRequestWarning: Unverified HTTPS request is being made to host 'data.stats.gov.cn'. Adding certificate verification is strongly advised. See: https://urllib3.readthedocs.io/en/latest/advanced-usage.html#ssl-warnings
这时,如果不希望收到警告信息,可以采用如下方式消除警告信息。
import urllib3
urllib3.disable_warnings()
总结:Python的库和requests
库是用于网络爬虫的常用工具。urllib
是Python标准库的一部分,提供了基本的URL处理功能,如URL编码、解码、解析等。然而,urllib
在处理HTTP请求和响应时功能相对有限,而requests
库则提供了更强大和灵活的HTTP客户端接口。
requests
库基于urllib
库,但提供了更高级的HTTP功能,如支持HTTPS、代理、会话、重定向等。此外,requests
库还提供了更友好的异常处理和响应内容处理,使得网络爬虫的开发更加方便和高效。
因此,虽然urllib
库是Python标准库的一部分,但在实际的网络爬虫开发中,requests
库通常更为常用。