Python爬虫之urllib请求库
一、前言
urllib 是 Python 内置的HTTP请求库, 不需要额外安装, 就可以直接使用。它提供了一系列用于操作URL的函数和类,可以用来发送请求、处理响应、解析URL等。
🔊:尽管现在很多人更喜欢使用requests库,但是了解 和 掌握 urllib 仍然很有必要, 因为它是很多其他库的基础,而且在一些特殊情况下可能会更有优势。而且它也是 requests 的底层库。
⚠️:在Python2中,有 urllib 和 urllib2 两个库实现HTTP请求的发送。而在Python3中,urllib2库已经不存在了,统一为urllib
在这篇文章里,我会详细介绍urllib的四个主要模块: request、error、parse 和 robotparser, 并通过实际的代码示例来展示它们的用法。
二、urllib 概述
🔊:urllib是 Python 标准库中用于 URL 处理的模块集合, 不需要通过 pip 安装。
它包含了多个处理URL的模块:
- urllib.request:这是最基本的HTTP请求模块,可以模拟请求的发送。就像在浏览器里输入网址然后按一下回车一样,只需要给库方法传入URL以及额外的参数,就可以模拟实现发送请求的过程了。
- urllib.error:异常处理模块。如果出现请求异常,那么我们可以捕获这些异常,然后进行重试或其他操作以保证程序运行不会意外终止。
- urllib.parse:一个工具模块。提供了许多URL的处理方法。例如拆分、解析、合并等。
- urllib.robotparser:主要用来识别网站的robots.txt文件,然后判断哪些网站可以爬,哪些网站不可以,它其实用的比较少。
这些模块提供了一系列强大的工具,可以帮助我们进行网络请求和URL处理。接下来,我们将逐一介绍这些模块的主要功能和使用方法。
1. urllib.request 模块
🔊:urllib.request 模块是urllib中最常用的模块,它提供了一系列函数和类来打开URL(主要是HTTP)。
我们可以使用这个模块来模拟浏览器发送GET和POST请求。
🌾 发送GET请求
使用urllib.request发送GET请求非常简单,我们可以使用urlopen()函数:
import urllib.request import gzip import io import ssl #全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context #发起请求 response = urllib.request.urlopen('https://www.python.org/')#urlopen 默认get请求 #获取响应头 content_type = response.headers.get('Content-Encoding') #读取数据 data = response.read() # 检查是否需要解压缩 if content_type == 'gzip': buf = io.BytesIO(data) with gzip.GzipFile(fileobj=buf) as f: data = f.read() print(data.decode('utf-8'))
这段代码会打开Python官网,并打印出网页的HTML内容。
🌾 发送POST请求
发送POST请求稍微复杂一些,我们需要使用Request对象:
import urllib.request import urllib.parse import ssl #全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context #Post请求参数 data = urllib.parse.urlencode({'name': 'John', 'age': 25}).encode('utf-8') #定义请求体 req = urllib.request.Request('http://httpbin.org/post', data=data, method='POST') #获取响应体 response = urllib.request.urlopen(req) print(response.read().decode('utf-8'))
这段代码向httpbin.org发送了一个POST请求,包含了name和age两个参数。
🌾 添加headers
在实际的爬虫中,我们常常需要添加headers来模拟浏览器行为。
可以在创建Request对象时添加headers:
import urllib.request import gzip import io import ssl #全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context #定义请求相关参数 #🌾请求地址 url = 'https://www.python.org/' #🌾请求头 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } #定义请求体 req = urllib.request.Request(url, headers=headers) #获取响应体 response = urllib.request.urlopen(req) # 获取响应头中的 Content-Encoding content_encoding = response.headers.get('Content-Encoding') # 读取数据 data = response.read() # 如果数据被 gzip 压缩,则需要解压 if content_encoding == 'gzip': buf = io.BytesIO(data) with gzip.GzipFile(fileobj=buf) as f: data = f.read() # 尝试使用 'utf-8' 解码 try: print(data.decode('utf-8')) except UnicodeDecodeError: print("Cannot decode data with 'utf-8' encoding.")
添加headers后,运行结果如下:
2. urllib.error 模块
urllib.error 模块定义了 urllib.request 可能抛出的异常类。主要有两个异常类:
- URLError: 由urllib.request产生的异常的基类。
- HTTPError: URLError的子类,用于处理HTTP和HTTPS URL的错误。
在进行网络请求时,可能会遇到各种异常情况。我们可以使用try-except语句来处理这些异常:
import urllib.request import urllib.error import gzip import io import ssl #全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context try: # 发送请求并获取响应 response = urllib.request.urlopen('https://www.python.org/') # 获取响应头中的 Content-Encoding content_encoding = response.headers.get('Content-Encoding') # 读取数据 data = response.read() # 如果数据被 gzip 压缩,则需要解压 if content_encoding == 'gzip': buf = io.BytesIO(data) with gzip.GzipFile(fileobj=buf) as f: data = f.read() # 尝试使用 'utf-8' 解码 print(data.decode('utf-8')) except urllib.error.URLError as e: print(f'URLError: {e.reason}') except urllib.error.HTTPError as e: print(f'HTTPError: {e.code}, {e.reason}') except UnicodeDecodeError: print("Cannot decode data with 'utf-8' encoding.")
这段代码会捕获 URLError 和 HTTPError ,这两种异常都定义在 urllib.error 模块中。
3. urllib.parse 模块
urllib.parse模块提供了许多URL处理的实用函数,例如解析、引用、拆分和组合。
🌾 URL解析
from urllib.parse import urlparse #网址 url = 'https://www.python.org/doc/?page=1#introduction' parsed = urlparse(url) #输出: #ParseResult(scheme='https', netloc='www.python.org', path='/doc/', params='', query='page=1', fragment='introduction') print(parsed) #hostname:www.python.org print(f'hostname:{parsed.hostname}') #Scheme: https print(f'Scheme: {parsed.scheme}') #Netloc: www.python.org print(f'Netloc: {parsed.netloc}') #Path: /doc/ print(f'Path: {parsed.path}') #Params: print(f'Params: {parsed.params}') #Query: page=1 print(f'Query: {parsed.query}') #Fragment: introduction print(f'Fragment: {parsed.fragment}')
🌾 URL编码和解码
在处理URL时,我们经常需要对参数进行编码和解码:
from urllib.parse import urlencode, unquote #🌾:编码 params = {'name': 'John Doe', 'age': 30, 'city': 'New York'} encoded = urlencode(params) print(f'Encoded: {encoded}') #Encoded: name=John+Doe&age=30&city=New+York #🌾:解码 decoded = unquote(encoded) print(f'Decoded: {decoded}') #Decoded: name=John+Doe&age=30&city=New+York
🔊:urlencode()函数将字典转换为URL编码的字符串, 而unquote()函数则进行解码。
🌾:拼接URL
from urllib.parse import urljoin #主地址 base_url = 'https://www.python.org/doc/' #路径 relative_url = 'tutorial/index.html' # 拼接URL full_url = urljoin(base_url, relative_url) print(full_url) #输出:https://www.python.org/doc/tutorial/index.html
🔊:urljoin()函数可以方便地将一个基础URL和相对URL拼接成一个完整的URL。
4. urllib.robotparser 模块
urllib.robotparser模块提供了一个RobotFileParser类,用于解析robots.txt文件。
🔊:robots.txt 是一个网站用来告诉爬虫哪些页面可以爬取,哪些不可以爬取的文件。
import ssl from urllib.robotparser import RobotFileParser #🌾:全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context #🌾:解析robots.txt文件 rp = RobotFileParser() rp.set_url('https://www.python.org/robots.txt') rp.read() #🌾:打印 #True print(rp.can_fetch('*', 'https://www.python.org/')) #True print(rp.can_fetch('*', 'https://www.python.org/admin/'))
这段代码会读取Python官网的robots.txt文件,然后检查是否允许爬取某些URL。
5. 实战示例: 爬取豆瓣电影Top250
现在,让我们用我们学到的知识来写一个实际的爬虫,爬取豆瓣电影Top250的信息。
import urllib.request import urllib.error import ssl import re from bs4 import BeautifulSoup #🌾:全局取消凭证 ssl._create_default_https_context = ssl._create_unverified_context #🌾 定义获取电影信息 def get_movie_info(url): headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36' } try: req = urllib.request.Request(url, headers=headers) response = urllib.request.urlopen(req) html = response.read().decode('utf-8') soup = BeautifulSoup(html, 'html.parser') movie_list = soup.find('ol', class_='grid_view') for movie_li in movie_list.find_all('li'): rank = movie_li.find('em').string title = movie_li.find('span', class_='title').string rating = movie_li.find('span', class_='rating_num').string if movie_li.find('span', class_='inq'): quote = movie_li.find('span', class_='inq').string else: quote = "N/A" print(f"Rank: {rank}") print(f"Title: {title}") print(f"Rating: {rating}") print(f"Quote: {quote}") print('-' * 50) except urllib.error.URLError as e: if hasattr(e, 'reason'): print(f'Failed to reach the server. Reason: {e.reason}') elif hasattr(e, 'code'): print(f'The server couldn\'t fulfill the request. Error code: {e.code}') # 爬取前5页 for i in range(5): url = f'https://movie.douban.com/top250?start={i*25}' get_movie_info(url)
这个爬虫会爬取豆瓣电影Top250的前5页,每页25部电影,共125部电影的信息。它使用了我们之前学到的urllib.request发送请求,使用BeautifulSoup解析HTML,并处理了可能出现的异常。
6. urllib vs requests
🔊:虽然 urllib 是Python的标准库,但在实际开发中, 很多人更喜欢使用 requests 库。
这是因为:
- 易用性: requests的API设计更加人性化,使用起来更加直观和简单。
- 功能强大: requests自动处理了很多urllib需要手动处理的事情,比如保持会话、处理cookies等。
- 异常处理: requests的异常处理更加直观和统一。
然而,urllib作为标准库仍然有其优势:
- 无需安装: 作为标准库,urllib无需额外安装即可使用。
- 底层操作: urllib提供了更多的底层操作,在某些特殊情况下可能更有优势。
在大多数情况下,如果你的项目允许使用第三方库,requests可能是更好的选择。但了解和掌握urllib仍然很有必要,因为它是Python网络编程的基础,而且在一些特殊情况下可能会更有用。
7. 注意事项
在使用urllib进行爬虫时,有一些重要的注意事项:
- 遵守robots.txt: 使用urllib.robotparser解析robots.txt文件,遵守网站的爬取规则。
- 添加合适的User-Agent: 在headers中添加合适的User-Agent,避免被网站识别为爬虫而被封禁。
- 控制爬取速度: 添加适当的延时,避免对目标网站造成过大压力。
- 处理异常: 正确处理可能出现的网络异常和HTTP错误。
- 解码响应: 注意正确解码响应内容,处理不同的字符编码。
- URL编码: 在构造URL时,注意对参数进行正确的URL编码。