目录
Python urllib 模块
urllib
模块是 Python 标准库,其价值在于抓取网络上的 URL 资源,入门爬虫时必学的一个模块。
不过更多的爬虫工程师上手学习的模块已经更换为 requests 了。
在 Python3 中 urllib 模块包括如下内容。
urllib.request
:请求模块,用于打开和读取 URL;urllib.error
:异常处理模块,捕获urllib.error
抛出异常;urllib.parse
:URL 解析,爬虫程序中用于处理 URL 地址;urllib.robotparser
:解析 robots.txt 文件,判断目标站点哪些内容可爬,哪些不可以爬,但是用的很少。
urllib.request.urlopen()函数
用于实现对目标url的访问。
from urllib.request import urlopen
with urlopen('https://www.example.net') as html:
page = html.read()
print(page)
上述代码用到了 urllib.requests
模块,其内部定义了打开 URL 的函数,授权验证的方法,重定向,cookie 操作等方法。
代码中用到的 urlopen()
函数,就是打开一个 URL,该函数的语法格式如下所示:
urllib.request.urlopen(url, data=None, timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
*, cafile=None, capath=None, cadefault=False, context=None)
参数描述如下所示:
url
:请求地址;data
:发送到服务器的其他数据对象,默认为 None;timeout
:超时时间;cafile
和capath
:cafile 为 CA 证书, capath 为 CA 证书的路径,使用 HTTPS 需要用到;context
:ssl.SSLContext 类型,用来指定 SSL 设置。
调用该对象的 read()
方法,可以读取到整个网页数据。
其余的函数与文件读取类似,分别是 readline()
,readlines()
。
还可以调用对象的 getcode()
方法,获取网页状态码。
print(html.getcode()) # 返回 200
urlopen() 返回对象的更多方法
使用 urlopen()
可以得到一个 HTTPResposne
类型的对象,它包括上文提及的 read()
方法,getcode()
方法,除此之外,还有如下内容可以使用。
getheaders()
:获取请求头内容;getheader(name)
:获取指定请求头;msg
:信息属性;version
:版本属性;status
:状态属性。
urllib.Request() 类
urllib .request
URL 请求抽象类,使用它可以扩展更多的请求配置,其构造方法如下所示:
def __init__(self, url, data=None, headers={},
origin_req_host=None, unverifiable=False,
method=None)
其参数说明如下所示:
url
:请求地址,必选参数;data
:请求参数,必须为bytes
类型数据,可以使用urlencode()
进行编码;headers
:请求头设置,字典类型;origin_req_host
:请求的主机地址,IP 或域名;method
:请求方法。
测试代码如下所示:
from urllib import request, parse
url = 'http://httpbin.org/post'
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) 你的UA'
}
dict = {
'name': 'xiangpica'
}
# 转换数据类型
data = bytes(parse.urlencode(dict), encoding='utf8')#将字典进行编码
# 实例化对象
req = request.Request(url=url, data=data, headers=headers, method='POST')
# 添加请求头
req.add_header('HOST', 'httpbin.org')
# 发送数据
response = request.urlopen(req)#实现对对象req的访问
print(response.read().decode('utf-8'))#将读取的网页数据按utf-8格式进行编码
- urllib.parse.urlencode () 编码工作使用urllib.parse的 urlencode () 函数,帮我们将 key:value 这样的键值对转换成 "key=value" 这样的字符串,解码工作可以使用urllib.parse的 unquote () 函数。
urllib.parse
该模块主要用于解析 URL,函数原型如下所示:
urllib.parse.urlparse(urlstring, scheme='', allow_fragments=True)
参数说明如下:
urlstring
:URL 地址;scheme
:协议类型,可用的包括 file、ftp、gopher、hdl、http、https、imap、mailto、 mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、 sip、sips、snews、svn、svn+ssh、telnet……;allow_fragments
:是否忽略 URL 中的fragment
部分。
标准的 URL 格式如下:
scheme://netloc/path;params?query#fragment
说明如下所示
scheme
:URL 协议;netloc
:域名和端口;path
:路径;params
:最后一个路径元素参数,不常用;query
:查询字符串;fragment
:片段标志。
from urllib.parse import urlparse
result = urlparse('http://www.example.com/index.html;info?id=10086#comment')
print(type(result), result)
print(result.scheme, result[0])
print(result.netloc, result[1])
print(result.path, result[2])
print(result.params, result[3])
print(result.query, result[4])
print(result.fragment, result[5])
- urlparse() 返回结果是一个
ParseResult
类型的对象。
其余内容
urlunparse()
方法与上述方法逻辑相反;urljoin()
方法用于拼接链接;urlencode()
:格式化请求参数;quote()
:将内容转换为 URL 编码格式,尤其是转换中文字符;unquote()
:对 URL 进行解码。
Python 正则表达式
正则表达式语法
正则表达式语法由字符和操作符构成,初期阶段掌握下述这些内容即可。
操作符 | 说明 | 例子 |
---|---|---|
. | 任何单个字符,极少不能匹配 | |
[] | 字符集,对单个字符给出取值范围 | [abc] 表示匹配 a、b、c,[a-z] 表示 a 到 z 单个字符 |
[^] | 非字符集,对单个字符给出排除范围 | [^abc] 表示匹配 非 a、非 b、非 c 的单个字符 |
* | 前一个字符 0 次或无限次扩展 | abc* 表示 ab、abc、abcc、abccc 等 |
+ | 前一个字符 1 次或无限次扩展 | abc+ 表示 abc、abcc、abccc 等 |
? | 前一个字符 0 次或 1 次 | abc? 表示 ab、abc |
| | 左右表达式任意一个 | abc|def 表示 abc 或者 def |
{m} | 扩展前 1 个字符 m 次 | ab{2}c,表示 abbc |
{m,n} | 扩展前 1 个字符 m 到 n 次 | ab{1,2}c,表示 abc、abbc |
^ | 匹配字符串开头 | ^abc 表示 abc 在字符串开头 |
$ | 匹配字符串结尾 | abc$ 表示 abc 在字符串结尾 |
() | 分组标记,内部仅能使用 | 操作符 | (abc) 表示 abc,(a |
\d | 数字,等价于 [0-9] | |
\w | 字符,等价于 [A-Za-z0-9] |
以上表示仅仅为正则表达最基础部分内容,如果希望深入研究正则表达式,建议寻找更加全面的资料进行学习。
re 库基本用法
re 库主要函数如下:
- 基础函数:
compile
; - 功能函数:
search
、match
、findall
、split
、finditer
、sub
。
原生字符串。
在 Python 中,表示原生字符串,需要在字符串前面加上 r
。
例如 my_str = 'i'am xiangpica'
在程序中会直接报错,
如果希望字符串中 '
可以正常运行,需要加上转移字符 \
,修改为 my_str = 'i\'am xiangpica'
。
- 但这样结合上文正则表达式中的操作符,就会出现问题,因为
\
在正则表达式中是有真实含义的,如果你使用 re 库去匹配字符串中的\
,那需要使用 4 个反斜杠,为了避免这种情况出现,引入了原生字符串概念。
# 不使用原生字符串的正则表达式 "\\\\"
# 使用原生字符串的正则表达式 r"\\"
- 例子
my_str='C:\number'
print(my_str)
C:
umber
本段代码的输出效果如下,\n
被解析成了换行,如果想要屏蔽这种现象,使用 r
即可:
my_str=r'C:\number'
print(my_str)
输出 C:\number
。
re.search 函数
该函数用于,在字符串中搜索正则表达式匹配到的第一个位置的值,返回 match 对象。
函数原型如下:
re.search(pattern,string,flags=0)
需求:在字符串 梦想橡皮擦 good good
中匹配 橡皮擦
。
import re
my_str='梦想橡皮擦 good good'
pattern = r'橡皮擦'
ret = re.search(pattern,my_str)
print(ret)
#输出匹配的结果
if ret:
print(ret.group(0))
<re.Match object; span=(2, 5), match='橡皮擦'> 橡皮擦
search
函数的第三个参数 flags
表示正则表达式使用时的控制标记。
re.I
,re.IGNORECASE
:忽略正则表达式的大小写;re.M
,re.MULTILINE
:正则表达式中的 ^ 操作符能够将给定字符串的每行当做匹配的开始;re.S
,re.DOTALL
:正则表达式中的.
操作符能够匹配所有字符。
re.match 函数
该函数用于将正则表达式与目标字符串的开始位置去匹配,返回 match 对象,未匹配成功返回 None,函数原型如下:
re.match(pattern,string,flags=0)
一定要注意是目标字符串开始位置。
import re
my_str = '梦想橡皮擦 good good'
pattern = r'梦' # 匹配到数据
pattern = r'good' # 匹配不到数据
ret = re.match(pattern, my_str)
if ret:
print(ret.group(0))
re.match
和 re.search
方法都是一次最多返回一个匹配对象,如果希望返回多个值,
可以通过在 pattern
里加括号构造匹配组返回多个字符串。
re.findall 函数
该函数用于搜索字符串,以列表格式返回全部匹配到的字符串,函数原型如下:
re.findall(pattern,string,flags=0)
测试代码如下:
import re
my_str = '梦想橡皮擦 good good'
pattern = r'good'
ret = re.findall(pattern, my_str)
print(ret)
re.split 函数
该函数将一个字符串按照正则表达式匹配结果进行分割,返回一个列表。
re.split(pattern, string, maxsplit=0, flags=0)
re.split
函数进行分割的时候,如果正则表达式匹配到的字符恰好在字符串开头或者结尾,
返回分割后的字符串列表首尾都多了空格,需要手动去除,例如下述代码:
import re
my_str = '1梦想橡皮擦1good1good1'
pattern = r'\d'
ret = re.split(pattern, my_str)
print(ret)
#['', '梦想橡皮擦', 'good', 'good', '']
切换为中间的内容,则能正确的分割字符串。
import re
my_str = '1梦想橡皮擦1good1good1'
pattern = r'good'
ret = re.split(pattern, my_str)
print(ret)
#['1梦想橡皮擦1', '1', '1']
如果在 pattern 中捕获到括号,那括号中匹配到的结果也会在返回的列表中。
import re
my_str = '1梦想橡皮擦1good1good1'
pattern = r'(good)'
ret = re.split(pattern, my_str)
print(ret)
#['1梦想橡皮擦1', 'good', '1', 'good', '1']
maxsplit
参数表示最多进行分割次数, 剩下的字符全部返回到列表的最后一个元素,
例如设置匹配 1 次,得到的结果是 ['1梦想橡皮擦1', '1good1']
。
re.finditer 函数
搜索字符串,并返回一个匹配结果的迭代器,每个迭代元素都是 match 对象。
函数原型如下:
re.finditer(pattern,string,flags=0)
测试代码如下:
import re
my_str = '1梦想橡皮擦1good1good1'
pattern = r'good'
# ret = re.split(pattern, my_str,maxsplit=1)
ret =re.finditer(pattern, my_str)
print(ret)
re.sub 函数
在一个字符串中替换被正则表达式匹配到的字符串,返回替换后的字符串,
函数原型如下:
re.sub(pattern,repl,string,count=0,flags=0)
其中 repl
参数是替换匹配字符串的字符串,count
参数是匹配的最大替换次数。
import re
my_str = '1梦想橡皮擦1good1good1'
pattern = r'good'
ret = re.sub(pattern, "nice", my_str)
print(ret)
运行之后,得到替换之后的字符串:
1梦想橡皮擦1nice1nice1
re 库其它函数
其它比较常见的函数有:re.fullmatch()
,re.subn()
,re.escape()
,
Beautiful Soup
Beautiful Soup 是一款 Python 解析库,主要用于将 HTML 标签转换为 Python 对象树,然后让我们从对象树中提取数据。
模块的安装
pip install bs4 -i 国内任意源即可
该模块包名称为 bs4
import requests
from bs4 import BeautifulSoup
def ret_html():
"""获取HTML元素"""
res = requests.get('https://www.crummy.com/software/BeautifulSoup/', timeout=3)
return res.text
if __name__ == '__main__':
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
print(soup)
在 BeautifulSoup
类的构造函数中传递的两个参数,一个是待解析的字符串,另一个是解析器,官方建议的是 lxml
,因其解析速度快。
import requests
from bs4 import BeautifulSoup
def ret_html():
"""获取HTML元素"""
res = requests.get('https://www.crummy.com/software/BeautifulSoup/', timeout=3)
return res.text
if __name__ == '__main__':
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
print(soup)
'''Site Search:
<form action="/search/" method="get">
<input maxlength="255" name="q" type="text" value=""/>
</form>
</td>
</tr>
</table>
</body>
</html>
'''
调用 soup
对象的 soup.prettify()
方法,可以将 HTML 标签进行格式化操作,这样你就可以在存储到外部文件的时候,让其 HTML 代码美观。
BeautifulSoup 模块的对象说明
BeautifulSoup 类可以将 HTML 文本解析成 Python 对象树,而这里面又包括最重要的四种对象,分别是 Tag
,NavigableString
,BeautifulSoup
,Comment
对象。
BeautifulSoup 对象
代表整个 HTML 页面,而且实例化该对象的时候,还会自动补齐 HTML 代码。
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
print(type(soup))
Tag 对象
Tag 是标签,Tag 对象就是网页标签,或者叫做网页元素对象,例如获取 bs4 官网的 h1 标签对象:
if __name__ == '__main__':
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
# print(soup.prettify()) # 格式化 HTML
print(soup.h1)
得到的也是网页中的 h1 标签:
<h1>Beautiful Soup</h1>
查看其类型:
print(soup.h1)
print(type(soup.h1))
得到的不是一个字符串,而是一个 Tag 对象。
<h1>Beautiful Soup</h1>
<class 'bs4.element.Tag'>
既然是 Tag 对象,那就会具备一些特定的属性值
- 获取标签名称
print(soup.h1)
print(type(soup.h1))
print(soup.h1.name) # 获取标签名称h1
- 通过 Tag 对象获取标签的属性值
print(soup.img) # 获取网页第一个 img 标签
print(soup.img['src']) # 获取网页元素DOM的属性值
#DOM 是 web 页面的完全的面向对象表述
<img align="right" src="10.1.jpg" width="250"/> 10.1.jpg
- 通过 attrs 属性获取标签的所有属性
print(soup.img) # 获取网页第一个 img 标签
print(soup.img.attrs) # 获取网页元素的所有属性值,以字典形式返回
{'align': 'right', 'src': '10.1.jpg', 'width': '250'}
<h1>Beautiful Soup</h1>
<class 'bs4.element.Tag'>
h1
<img align="right" src="10.1.jpg" width="250"/>
{'align': 'right', 'src': '10.1.jpg', 'width': '250'}
NavigableString 对象
NavigableString 对象获取的是标签内部的文字内容,例如 p
标签,在下述代码中提取的是 我是橡皮擦
<p>我是橡皮擦</p>
获取该对象也非常容易,使用 Tag 对象的 string 属性即可。
nav_obj = soup.h1.string
print(type(nav_obj))
输出结果如下所示
<class 'bs4.element.NavigableString'>
如果目标标签是一个单标签,会获取到 None 数据
除了使用对象的 string
方法外,还可以使用 text
属性和 get_text()
方法来获取标签内容
print(soup.h1.text)
print(soup.p.get_text())
print(soup.p.get_text('&'))
其中 text
是获取所有子标签内容的合并字符串,而 get_text()
也是相同的效果,不过使用 get_text()
可以增加一个分隔符,例如上述代码的 &
符号,还可以使用,strip=True 参数去除空格。
Comment 对象
获取网页注释内容,用处不大,忽略即可。
BeautifulSoup 对象和 Tag 对象支持标签查找方法,具体内容如下所示。
find() 方法和 find_all() 方法
调用 BeautifulSoup 对象和 Tag 对象的 find()
方法,可以在网页中找到指定对象,该方法的语法格式如下:
obj.find(name,attrs,recursive,text,**kws)
方法的返回结果是查找到的第一个元素,如果没查询到,返回 None。
参数说明如下:
name
:标签名称;attrs
:标签属性;recursive
:默认搜索所有后代元素;text
:标签内容。
例如我们继续在上文请求的网页中,查找 a
标签,代码如下:
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
print(soup.find('a'))
也可以使用 attrs
参数进行查找,代码如下:
html_str = ret_html()
soup = BeautifulSoup(html_str, 'lxml')
# print(soup.find('a'))
print(soup.find(attrs={'class': 'cta'}))
find()
方法还提供了一些特殊的参数,便于直接查找,例如可以使用 id=xxx
,查找属性中包含 id
的标签,可以使用 class_=xxx
,查找属性中包含 class
的标签。
print(soup.find(class_='cta'))
与 find()
方法成对出现的是 find_all()
方法,看名称就能知道其返回结果收是全部匹配标签,语法格式如下:
obj.find_all(name,attrs,recursive,text,limit)
其中重点说明一下 limit
参数,它表示最多返回的匹配数量,find()
方法可以看作 limit=1
,这样就变得容易理解了。