参考 B 站IT 私塾Python 爬虫编程基础 5 天速成(2021 全新合集)Python 入门+数据分析视频

初识网络爬虫

网络爬虫(又被称为网页蜘蛛、网络机器人),是一种按照一定的规则,自动地抓取互联网信息的程序或者脚本。

本质是,由一个入口网页进行深入,不停的进行其他的 URL 的爬取,然后再把抓取到的网页进行分析处理得出想要的数据。

Robots 协议,是网站跟爬虫间的协议,用简单直接的 txt 格式文本方式告诉对应的爬虫被允许的权限

网络爬虫的一般流程

  1. 发起请求,获取响应

    通过 http 库,对目标站点进行请求。等同于自己打开浏览器,输入网址

    常用库:urllib、urllib3、requests

    服务器会返回请求的内容,一般为:html、二进制文件(视频,音频)、文档,json 字符串

  2. 解析内容

    寻找自己需要的信息,就是利用正则表达式或者其他库提取目标信息

    常用库:re、beautifulsoup4

  3. 数据持久化
    将解析得到的数据保存到文件或者数据库中


基本流程

准备工作

  • 弄清楚你需要爬取哪些页面,以及哪的数据

分析页面

借助 Chrome 开发者工具(F12)来分析网页,在 Elements 下找到需要的数据位置

编码规范

  • 一般 Python 程序第一行需要加入:#-*- coding:utf-8 -*-或者# coding=utf-8。这样可以在代码中包含中文
  • 使用函数实现单一功能或相关联功能的代码段,可以提高可读性和代码重复利用率,函数代码块以 def 关键词开头。
  • 可以加入 main 函数用于测试程序:if __name__ == "__main__":
  • 使用 # 添加注释,说明代码段的作用

引入模块

模块 module:用来从逻辑上组织 Python 代码(变量、函数、类),本质就是 py 文件,提高代码的可维护性。

module 可看作一个工具类,可共用或者隐藏代码细节,将相关代码放置在一个 module 以便让代码更好用、易懂,让 coder 重点放在高层逻辑上。

module 能定义函数、类、变量,也能包含可执行的代码。module 来源有 3 种:

  1. Python 内置的模块(标准库)

  2. 第三方模块

  3. 自定义模块

包 package: 为避免模块名冲突,Python 引入了按目录组织模块的方法。包是含有 Python 模块的文件夹。

例子:

1
2
import sys
from bs4 import BeautifulSoup

获取数据

  • 对每一个页面,调用 askURL 函数获取页面内容
  • 定义一个获取页面的函数 askURL,传入一个 url 参数,表示网址
  • urllib.Request生成请求。urllib.urlopen发送请求获取响应。read 获取页面内容。
  • 在访问页面时经常会出现错误,为了程序正常运行,加入异常捕获try...except...语句

urllib 是 python 标准库,直接使用

request.urlopen():接收一个 url 或一个 request 对象,向目标发起请求

1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib import request

url = "https://www.baidu.com"

response = request.urlopen(url) # 访问该url并获得响应

print(response.geturl()) # 获取主机地址
print(response.getcode()) # 获取请求状态码
print(response.info()) # 获取响应头

html = response.read() # 获取的是字节形式的内容
html.decode("utf-8") # 解码
print(html)
常见的HTTP状态码
状态码英文名称中文描述
200OK请求成功。一般用于 GET 与 POST 请求
301Moved Permanently永久移动。请求的资源已被永久的移动到新 URI,返回信息会包括新的 URI,浏览器会自动定向到新 URI。今后任何新的请求都应使用新的 URI 代替
404Not Found服务器无法根据客户端的请求找到资源(网页)。通过此代码,网站设计人员可设置"您所请求的资源无法找到"的个性页面
500Internal Server Error服务器内部错误,无法完成请求

想了解状态码更多?

urllib 模块

这最基本的请求,是 python 内置的一个 http 请求库,不需要额外的安装。只需要关注请求的链接、参数,提供了强大的解析。

urllb.request 请求模块

urllib.error 异常处理模块

urllib.parse 解析模块

用法讲解

  1. 简单的一个 get 请求:
1
2
3
import urllib.request
reponse = urllib.request.urlopen('http://www.baidu.com')
print(reponse.read().decode('utf-8'))
  1. 简单的一个 post 请求
1
2
3
4
5
import urllib.parse
import urllib.request
data = bytes(urllib.parse.urlencode({'hello':'world'}),encoding='utf-8')
reponse = urllib.request.urlopen('http://httpbin.org/post',data=data)
print(reponse.read())
  1. 超时处理
1
2
3
import urllib.request
response = urllib.request.urlopen('http://httpbin.org/get',timeout=1)
print(response.read())
  1. 由于使用 urlopen 无法传入参数,我们需要声明一个 request 对象,通过这个对象来添加参数
1
2
3
4
import urllib.request
request = urllib.request.Request('https://python.org') #由于urlopen无法传参数,声明一个Request对象
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
  1. 我们还可以分别创建字符串、字典等等来带入到 request 对象里面
1
2
3
4
5
6
7
8
9
10
11
12
13
from urllib import request,parse
url='http://httpbin.org/post'
headers={
'user-agent': 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36(KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36',
'Host':'httpbin.org'
}
dict={
'name':'jay'
}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req=request.Request(url=url,data=data,headers=headers,method='POST')
response=request.urlopen(req)
print(response.read().decode('utf-8'))
  1. 还可通过 addheaders 方法不断地向原始的 requests 对象里不断添加
1
2
3
4
5
6
7
8
9
10
from urllib import request,parse
url ='http://httpbin.org/post'
dict = {
'name':'cq'
}
data=bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url,data=data,method='POST')
req.add_header('user-agent', 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36')
response=request.urlopen(req)
print(response.read().decode('utf-8')

解析内容

  • 使用 BeautifulSoup 定位特定的标签位置
  • 使用正则表达式找到具体的内容

标签解析

BeautifulSoup 是一个库,提供一些简单的、python 式的用来处理导航、搜索、修改分析树等功能,通过解析文档为用户提供需要抓取的数据。我们需要的每个电影都在一个<div>的标签中,且每个 div 标签都有一个属性 class= "item"。

BeautifulSoup 模块

简介

BeautifulSoup4 和 lxml 一样,Beautiful Soup 也是一个 HTML/XML 的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。

BeautifulSoup 支持 Python 标准库中的 HTML 解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python 默认的解析器,lxml 解析器更加强大,速度更快,推荐使用 lxml 解析器。

Beautiful Soup 自动将输入文档转换为 Unicode 编码,输出文档转换为 utf-8 编码。你不需要考虑编码方式,除非文档没有指定一个编码方式,这时,Beautiful Soup 就不能自动识别编码方式了。然后,你仅仅需要说明一下原始编码方式就可以了。

使用

假设有这样一个 Html,具体内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<!DOCTYPE html>
<html>
<head>
<meta content="text/html;charset=utf-8" http-equiv="content-type" />
<meta content="IE=Edge" http-equiv="X-UA-Compatible" />
<meta content="always" name="referrer" />
<link
href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css"
rel="stylesheet"
type="text/css"
/>
<title>百度一下,你就知道</title>
</head>
<body link="#0000cc">
<div id="wrapper">
<div id="head">
<div class="head_wrapper">
<div id="u1">
<a class="mnav" href="http://news.baidu.com" name="tj_trnews"
>新闻
</a>
<a class="mnav" href="https://www.hao123.com" name="tj_trhao123"
>hao123
</a>
<a class="mnav" href="http://map.baidu.com" name="tj_trmap"
>地图
</a>
<a class="mnav" href="http://v.baidu.com" name="tj_trvideo"
>视频
</a>
<a class="mnav" href="http://tieba.baidu.com" name="tj_trtieba"
>贴吧
</a>
<a
class="bri"
href="//www.baidu.com/more/"
name="tj_briicon"
style="display: block;"
>更多产品
</a>
</div>
</div>
</div>
</div>
</body>
</html>

创建 beautifulsoup4 对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser") # 缩进格式

print(bs.prettify()) # 格式化html结构
print(bs.title) # 获取title标签的名称
print(bs.title.name) # 获取title的name
print(bs.title.string) # 获取head标签的所有内容
print(bs.head)
print(bs.div) # 获取第一个div标签中的所有内容
print(bs.div["id"]) # 获取第一个div标签的id的值
print(bs.a)
print(bs.find_all("a")) # 获取所有的a标签
print(bs.find(id="u1")) # 获取id="u1"

for item in bs.find_all("a"):
print(item.get("href")) # 获取所有的a标签,并遍历打印a标签中的href的值

for item in bs.find_all("a"):
print(item.get_text())

BeautifulSoup4 四大对象种类

BeautifulSoup4 将复杂 HTML 文档转换成一个复杂的树形结构,每个节点都是 Python 对象,所有对象可以归纳为 4 种:

  • Tag
  • NavigableString
  • BeautifulSoup
  • Comment
Tag

Tag 通俗点讲就是 HTML 中的一个个标签,例如:

1
2
3
4
5
6
7
8
9
10
11
12
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# 获取title标签的所有内容
print(bs.title)
# 获取head标签的所有内容
print(bs.head)
# 获取第一个a标签的所有内容
print(bs.a)
# 类型
print(type(bs.a))

我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是 bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。

对于 Tag,它有两个重要的属性,是 name 和 attrs:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
# [document] #bs 对象本身比较特殊,它的 name 即为 [document]
print(bs.name)
# head #对于其他内部标签,输出的值便为标签本身的名称
print(bs.head.name)
# 在这里,我们把"a"标签的所有属性打印输出了出来,得到的类型是一个字典。
print(bs.a.attrs)
#还可以利用get方法,传入属性的名称,二者是等价的
print(bs.a['class']) # 等价 bs.a.get('class')
# 可以对这些属性和内容等等进行修改
bs.a['class'] = "newClass"
print(bs.a)
# 还可以对这个属性进行删除
del bs.a['class']
print(bs.a)

既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如:

1
2
3
4
5
6
7
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")

print(bs.title.string)
print(type(bs.title.string))
BeautifulSoup

BeautifulSoup 对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性,例如:

1
2
3
4
5
6
7
8
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()

bs = BeautifulSoup(html,"html.parser")
print(type(bs.name))
print(bs.name)
print(bs.attrs)
Comment

Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。

1
2
3
4
5
6
7
8
9
from bs4 import BeautifulSoup
file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html,"html.parser")
print(bs.a)
# 此时不能出现空格和换行符,a标签如下:
# <a class="mnav" href="http://news.baidu.com" name="tj_trnews"><!--新闻--></a>
print(bs.a.string) # 新闻
print(type(bs.a.string)) # <class 'bs4.element.Comment'>

遍历文档树

功能
.contents获取 Tag 的所有子节点,返回一个 list
.children获取 Tag 的所有子节点,返回一个生成器
.descendants获取 Tag 的所有子孙节点
.strings如果 Tag 包含多个字符串,即在子孙节点中有内容,可以用此获取,而后进行遍历
.stripped_strings与 strings 用法一致,只不过可以去除掉那些多余的空白内容
.parent获取 Tag 的父节点
.parents递归得到父辈元素的所有节点,返回一个生成器
.previous_sibling获取当前 Tag 的上一个节点,属性通常是字符串或空白,真实结果是当前标签与上一个标签之间的顿号和换行符
.next_sibling获取当前 Tag 的下一个节点,属性通常是字符串或空白,真是结果是当前标签与下一个标签之间的顿号与换行符
.previous_siblings获取当前 Tag 的上面所有的兄弟节点,返回一个生成器
.next_siblings获取当前 Tag 的下面所有的兄弟节点,返回一个生成器
.previous_element获取解析过程中上一个被解析的对象(字符串或 tag),可能与 previous_sibling 相同,但通常是不一样的
.next_element获取解析过程中下一个被解析的对象(字符串或 tag),可能与 next_sibling 相同,但通常是不一样的
.previous_elements返回一个生成器,可以向前访问文档的解析内容
.next_elements返回一个生成器,可以向后访问文档的解析内容
.has_attr判断 Tag 是否包含属性

搜索文档树

find_all(name, attrs, recursive, text, **kwargs)

在上面的栗子中我们简单介绍了 find_all 的使用,接下来介绍一下 find_all 的更多用法-过滤器。这些过滤器贯穿整个搜索 API,过滤器可以被用在 tag 的 name 中,节点的属性等。

(1)name 参数

字符串过滤:会查找与字符串完全匹配的内容

bs.find_all("a")

正则表达式过滤:如果传入的是正则表达式,那么 BeautifulSoup4 会通过 search()来匹配内容

bs.find_all(re.compile("a"))

列表:如果传入一个列表,BeautifulSoup4 将会与列表中的任一元素匹配到的节点返回

bs.find_all(["meta","link"])

方法:传入一个方法,根据方法来匹配

下例是判断标签里是否含有 name 属性

1
2
3
def name_is_exists(tag):
return tag.has_attr("name")
t_list = bs.find_all(name_is_exists)

(2)attrs 参数

并不是所有的属性都可以使用上面这种方式进行搜索,比如 HTML 的data-*属性:

t_list = bs.find_all(data-foo="value")

如果执行这段代码,将会报错。我们可以使用 attrs 参数,定义一个字典来搜索包含特殊属性的 tag:

t_list = bs.find_all(attrs={"data-foo":"value"})

(3)text 参数

通过 text 参数可以搜索文档中的字符串内容,与 name 参数的可选值一样,text 参数接受 字符串,正则表达式,列表

1
2
3
4
t_list = bs.find_all(attrs={"data-foo": "value"})
t_list = bs.find_all(text="hao123")
t_list = bs.find_all(text=["hao123", "地图", "贴吧"])
t_list = bs.find_all(text=re.compile("\d"))

当我们搜索 text 中的一些特殊属性时,同样也可以传入一个方法来达到我们的目的:

1
2
3
def length_is_two(text):
return text and len(text) == 2
t_list = bs.find_all(text=length_is_two)

(4)**kwargs 参数

1
2
3
4
5
6
# 查询id=head的Tag
t_list = bs.find_all(id="head")
# 查询href属性包含 http://baidu.com 的Tag
t_list = bs.find_all(href=re.compile("http://baidu.com"))
# 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
t_list = bs.find_all(class_=True)

(5)limit 参数

可以传入一个 limit 参数来限制返回的数量,当搜索出的数据量为 5,而设置了 limit=2 时,此时只会返回前 2 个数据

bs.find_all("a",limit=2)

find_all 除了上面一些常规的写法,还可以对其进行一些简写:

bs.find_all("a") 👉 bs("a")

bs.a.find_all(text="新闻") 👉 bs.a(text="新闻")

find()

find()将返回符合条件的第一个 Tag,有时我们只需要或一个 Tag 时,我们就可以用到 find()方法了。当然了,也可以使用 find_all()方法,传入一个 limit=1,然后再取出第一个值也是可以的,不过未免繁琐。

bs.find_all("title",limit=1) 👉 bs.find("title")

从结果可以看出 find_all,尽管传入了 limit=1,但是返回值仍然为一个列表,当我们只需要取一个值时,远不如 find 方法方便。但

是如果未搜索到值时,将返回一个 None。

CSS 选择器

BeautifulSoup 支持发部分的 CSS 选择器,在 Tag 获取 BeautifulSoup 对象的.select()方法中传入字符串参数,即可使用 CSS 选择器的语法找到 Tag:

(1)通过标签名 Tag 查找

bs.select('title')

(2)通过类名 Class 查找

bs.select('.hassan')

(3)通过 id 查找

bs.select('#hassan')

(4)组合查找

bs.select('div .bri')

(5)属性查找

bs.select('a[class="bri"]')

bs.select('a[href="http://baidu.com"]')

(6)直接子标签查找

bs.select("head > title")

(7)兄弟节点标签查找

bs.select(".head ~ .title")

(8)获取内容

1
2
t_list = bs.select("title")
print(bs.select('title')[0].get_text())

正则提取

正则表达式:通常被用来检索、替换那些符合某个模式(规则)的文本。正则表达式是对字符串操作的一种逻辑公式,就是用事先定义好的一些特定字符及这些特定字符的组合,组成一个“规则字符串”,这个“规则字符串”用来表达对字符串的一种过滤逻辑。Python 中使用re 模块操作正则表达式。

re 模块

Re 库主要功能函数

函数说明
re.search()在一个字符串中搜索匹配正则表达式的第一个位置,返回 match 对象(左闭右开,首标为 0)
re.match()从一个字符串的开始位置起匹配正则表达式,返回 match 对象
re.findall()搜索字符串,以列表类型返回全部能匹配的子串
re.split()将一个字符串按照正则表达式匹配结果进行分割,返回列表类型
re.finditer()搜索字符串,返回一个匹配结果的迭代类型,每个迭代元素是 match 对象
re.sub()在一个字符串中替换所有匹配正则表达式的子串,返回替换后的字符串

正则表达式可以包含一些可选标志修饰符来控制匹配的模式。修饰符被指定为一个可选的标志。多个标志可以通过按位 OR(|) 它们来指定。如 re.I | re.M 被设置成 I 和 M 标志:

修饰符描述
re.l使匹配对大小写不敏感
re.L使本地化识别(locale-aware)匹配
re.M多行匹配,影响^和$
re.S使"."忽视包括换行在内的所有字符
re.U根据 Unicode 字符集解析字符。这个标志影响 \w, \W, \b, \B
re.X该标志通过给予你更灵活的格式以便你将正则表达式写得更易于理解

re 模块下的函数

compile(pattern):创建模式对象

1
2
3
4
5
6
import re
pat=re.compile("AA")
m=pat.search("ABC")
#等价于 re.search("AA", "CBA")
#<re.Match object; span=(2, 3), match='A'> 表示匹配到了
print(m) #None 表示没匹配到

search(pattern,string):在字符串中寻找模式

1
2
3
4
5
6
import re
m = re.search("asd" , "ASDasd" )
print(m)
# <_sre.SRE_Match object at 0xb72cd6e8> #匹配到了,返回MatchObject(True)
m = re.search("asd" , "ASDASD" )
print(m) #没有匹配到,返回None(False)

match(pattern,string):在字符串开始处匹配模式

1
2
3
4
5
6
7
8
9
10
11
# 等价于
pat=re.compile("a")
print(pat.match("Aasd"))
#输出None
print(pat.match("aASD"))
#输出 <_sre.SRE_Match object at 0xb72cd6e8>
# 上面的函数返回都可以在if条件语句中进行判断:
if pat.search("asd"):
print ("OK") #OK #找到返回
if re.search("a","ASD" ):
print ("OK") #没有找到

split(pattern,string):根据模式分割字符串,返回列表

1
2
3
4
5
6
7
8
9
10
11
12
re.split( , , a,s,d,asd )
["a","s","d","asd"] #返回列表
pat = re.compile( , )
pat.split( a,s,d,asd )
["a","s","d","asd"] #返回列表
re.split( [, ]+ ,"a","s",d ,,,,,asd ) #正则匹配:[, ]+,后面说明
["a","s","d","asd"]
re.split( [, ]+ ,"a","s",d ,,,,,asd ,maxsplit=2) # maxsplit 最多分割次数
["a","s","d",,,,,asd ]
pat = re.compile( [, ]+ ) #正则匹配:[, ]+,后面说明
pat.split("a","s",d ,,,,,asd ,maxsplit=2) # maxsplit 最多分割次数
["a","s","d",,,,,asd ]

findall(pattern,string):列表形式返回匹配项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import re
print(re.findall("a","ASDaDFGAa"))
#["a","a"] #列表形式返回匹配到的字符串
pat = re.compile("a")
print(pat.findall("ASDaDFGAa"))
#["a","a"] #列表形式返回匹配到的字符串
pat = re.compile("[A-Z]+") #正则匹配:[A-Z]+ 后面有说明
print(pat.findall("ASDcDFGAa"))
#["ASD","DFGA"] #找到匹配到的字符串
pat = re.compile([A-Z])
pat.findall("ASDcDFGAa") #正则匹配:[A-Z]+ 后面有说明
["A","S","D","D","F","G","A"] #找到匹配到的字符串
pat = re.compile( [A-Za-z] ) #正则匹配:[A-Za-z]+ 匹配所有单词,后面有说明
pat.findall("ASDcDFGAa")
["A","S","D","c","D","F","G","A","a"]

sub(pat,repl,string) :用 repl 替换 pat 匹配项

(留的是中间的,因为中间在中心)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
re.sub("a","A","abcasd") #找到a用A替换,后面见和group的配合使用
AbcAsd
pat = re.compile("a")
pat.sub("A","abcasd")
AbcAsd
pat=re.compile(r www.(.*)..{3} ) #正则表达式
#在Python的string前面加上‘r’, 是为了告诉编译器这个string是个raw string,不要转译反斜杠
#例如, 在raw string中,是两个字符,和n, 而不会转译为换行符。
#由于正则表达式和 会有冲突,因此,当一个字符串使用了正则表达式后,最好在前面加上 r 。
#与大多数编程语言相同,正则表达式里使用""作为转义字符,这就可能造成反斜杠困扰。
#假如你需要匹配文本中的字符"",那么使用编程语言表示的正则表达式里将需要4个反斜杠"\\":
#前两个和后两个分别用于在编程语言里转义成反斜杠,转换成两个反斜杠后再在正则表达式里转义成一个
反斜杠。
#Python里的原生字符串很好地解决了这个问题,这个例子中的正则表达式可以使用r"\"表示。
#同样,匹配一个数字的"\d"可以写成r"d"。
#有了原生字符串,你再也不用担心是不是漏写了反斜杠,写出来的表达式也更直观。
#不是说 加了r 就没有转译功能,好乱,就直接记住1句话:
#当一个字符串使用了正则表达式后,最好在前面加上 r ,这样你再也不用担心是不是漏写了反斜杠,写
出来的表达式也更直观
pat.match( www.dxy.com ).group(1)
dxy
re.sub(r www.(.*)..{3} ,r , hello,www.dxy.com )
pat.sub(r , hello,www.dxy.com )
hello,dxy
# r 1 是第一组的意思
#通过正则匹配找到符合规则的"www.dxy.com" ,取得 组1字符串 去替换 整个匹配。
pat=re.compile(r (w+) (w+) ) #正则表达式
s= hello world ! hello hz !
pat.findall( hello world ! hello hz ! )
[( hello , world ), ( hello , hz )]
pat.sub(r ,s) #通过正则得到组1(hello),组2(world),再通过sub去替换。
即组1替换组2,组2替换组1,调换位置。
world hello!hz hello!

escape(string) :对字符串里面的特殊字符串进行转义

1
2
re.escape( www.dxy.cn )
www\.dxy\.cn #转义

上面的函数中,只有 match、search 有 group 方法,其他的函数没有。

函数的方法

group:获取子模式(组)的匹配项

1
2
3
4
5
6
7
8
pat = re.compile(r www.(.*).(.*) ) #用()表示1个组,2个组
m = pat.match( www.dxy.com )
m.group() #默认为0,表示匹配整个字符串
www.dxy.com
m.group(1) #返回给定组1匹配的子字符串
dxy
m.group(2)
com

start:给定组匹配项的开始位置

1
2
m.start(2) #组2开始的索引
8

end:给定组匹配项的结束位置

1
2
m.end(2) #组2结束的索引
11

span:给定组匹配项的开始结束位置

1
2
m.span(2) #组2开始、结束的索引
(8, 11)

正则表达式

元字符
“.” :通配符,除换行符外的任意的 1 个字符

1
2
3
4
5
6
7
8
9
10
11
pat=re.compile(".")
pat.match("abc")
<_sre.SRE_Match object at 0xb72b6170>
pat.match("abc").group()
a #匹配到了首个字符
pat.search("abc").group()
a
pat.match( ).group() #换行符匹配出错
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AttributeError: NoneType object has no attribute group

“” : 转义符

1
2
3
4
5
pat=re.compile(".")
pat.search( abc.efg ).group() #匹配到.
.
pat.findall( abc.efg ) #不用group,返回列表
["."]

“[…]” : 字符集合,匹配里面的任意一个元素

1
2
3
4
5
6
7
pat=re.compile( [abc] )
pat.match("axbycz").group()
a
pat.search("axbycz").group()
a
pat.findall("axbycz")
["a","b","c"]

“d” : 数字

1
2
3
4
5
6
7
8
9
10
>>> pat=re.compile("d")
>>> pat.search( ax1by2cz3 ).group() #匹配到第一个数字:1,返回
>>> 1
>>> pat.match( ax1by2cz3 ).group() #匹配不到(首个不是)返回None,报错,match匹配字
符串头
>>> Traceback (most recent call last):
>>> File "<stdin>", line 1, in <module>
>>> AttributeError: NoneType object has no attribute group
>>> pat.findall( ax1by2cz3 ) #匹配所有的数字,列表返回
>>> [ 1 , 2 , 3 ]

“D” : 非数字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
>>> pat=re.compile("D")
>>> pat.match( ax1by2cz3 ).group()
>>> a
>>> pat.search( ax1by2cz3 ).group()
>>> a
>>> pat.findall( ax1by2cz3 )
>>> ["a", x ,"b", y ,"c", z ]
>>> 1
>>> 2
>>> 3
>>> 4
>>> 5
>>> 6
>>> 7
>>> “s” :空白字符 、 、
>>>
>>> pat=re.compile("D")
>>> pat.match( ax1by2cz3 ).group()
>>> a
>>> pat.search( ax1by2cz3 ).group()
>>> a
>>> pat.findall( ax1by2cz3 )
>>> ["a", x ,"b", y ,"c", z ]
>>> 1
>>> 2
>>> 3
>>> 4
>>> 5
>>> 6
>>> 7
>>> “s” :空白字符 、 、
>>>

“S” :非空白字符

1
2
3
4
5
>>> pat=re.compile("S")
>>> pat.search( ax1 by2 cz3 ).group()
>>> a
>>> pat.findall( ax1 by2 cz3 )
>>> ["a", x , 1 ,"b", y , 2 ,"c", z , 3 ]

“w” :单个的 数字和字母,[A-Za-z0-9]

1
2
3
4
5
6
>>> pat=re.compile( w )
>>> pat.search( 1a2b3c ).group()
>>> 1
>>> pat.findall( 1a2b3c )
>>> [ 1 ,"a", 2 ,"b", 3 ,"c"]
>>> pat.match( 1a2b3c ).group()

“W”:非单词字符,除数字和字母外

1
2
3
4
5
>>> pat=re.compile( W )
>>> pat.findall( 1a2我b3c ) #python是用三字节表示一个汉字
>>> [ æ , ˆ , ‘ ]
>>> pat.search( 1a2我b3c ).group()
>>> æ

数量词
“*” :0 次或多次
( 乘 0 会变成 0)

1
2
3
4
5
6
7
>>> pat = re.compile( [abc]* )
>>> pat.match( abcabcdefabc ).group()
>>> abcabc #2次
>>> pat.search( abcabcdefabc ).group()
>>> abcabc #2次
>>> pat.findall( abcabcdefabc )
>>> [ abcabc , , , ,"abc", ] #2次和1次,因为有0次,所以匹配了

“+” :1 次或多次

1
2
3
4
5
6
7
>>> pat = re.compile( [abc]+ )
>>> pat.match( abcdefabcabc ).group()
>>> abc
>>> pat.search( abcdefabcabc ).group()
>>> abc
>>> pat.findall( abcdefabcabc )
>>> ["abc", abcabc ]

“?” :0 次或 1 次,match,search 不会出现 none,会出现’ ‘ (因为 0 次也是符合的)
0 次或 1 次不是指[xxx]这个集合,而是其中的任何的一个字符

1
2
3
4
5
6
7
>>> pat = re.compile( [abc]? )
>>> pat.match( defabc ).group() #0次
>>> pat.match( abcdefabc ).group()
>>> a
>>> pat.search( defabc ).group() #0次
>>> pat.findall( defabc ) #0次和1次
>>> [ , , ,"a","b","c", ] #后面总再加个

“数量词?” :非贪婪模式:只匹配最少的(尽可能少);默认贪婪模式:匹配最多的(尽可能多)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>>> pat = re.compile( [abc]+ ) #贪婪模式
>>> pat.match( abcdefabcabc ).group() #匹配尽可能多的:abc
>>> abc
>>> pat.match( bbabcdefabcabc ).group()
>>> bbabc
>>> pat.search( dbbabcdefabcabc ).group()
>>> bbabc
>>> pat.findall( abcdefabcabc )
>>> ["abc", abcabc ]
>>> pat = re.compile( [abc]+? ) #非贪婪模式:+?
>>> pat.match( abcdefabcabc ).group() #匹配尽可能少的:a、b、c
>>> a
>>> pat.search( dbbabcdefabcabc ).group()
>>> b
>>> pat.findall( abcdefabcabc )
>>> ["a","b","c","a","b","c","a","b","c"]

“{m}” :匹配字符串出现 m 次

1
2
3
4
5
>>> pat = re.compile( [op]{2} ) #o或p出现2次
>>> pat.search( abcooapp ).group() #匹配第一次出现的字符串,o比p先出现
>>> oo
>>> pat.findall( abcooapp ) #匹配出现的所有字符串,列表形式返回
>>> [ oo , pp ]

“{m,n}” :匹配字符串出现 m 到 n 次

1
2
3
4
5
6
7
>>> pat = re.compile( [op]{2,4} ) #o或则p出现2到4次
>>> pat.match( pppabcooapp ).group() #匹配开头
>>> ppp
>>> pat.search( pppabcooapp ).group() #匹配第一次出现
>>> ppp
>>> pat.findall( pppabcooapp ) #匹配所有
>>> [ ppp , oo , pp ]

.group() #匹配第一次出现
边界
“^” :匹配字符串开头或行头

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>>> pat = re.compile( ^[abc] ) #开头是a、b、c中的任意一个
>>> pat.search( defabc ).group()
>>> pat.match( defabc ).group() #均找不到
>>> pat.findall( defabc )
>>> []
>>> pat.search( adefabc ).group()
>>> a
>>> pat.match( adefabc ).group() #开头是a、b、c中的任意一个
>>> a
>>> pat.findall( adefabc )
>>> ["a"]
>>> pat = re.compile( ^[abc]+ ) #开头是a、b、c中的任意一个的一次或则多次,贪婪:匹配
多个
>>> pat.findall( cbadefab )
>>> [ cba ]
>>> pat = re.compile(r ^[abc]+? ) #开头是a、b、c中的任意一个的一次或则多次,非贪婪:匹
配一个
>>> pat.findall( cbadefab )
>>> ["c"]

“$” :匹配字符串结尾或则行尾

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> pat = re.compile( [abc]$ )
>>> pat.match( adefAbc ).group() #match匹配的是字符串开头,所以查找$的时,总是返回
None
>>> pat.search( adefAbc ).group() #结尾是a、b、c中的任意一个
>>> c
>>> pat.findall( adefAbc )
>>> ["c"]
>>> pat = re.compile( [abc]+$ )
>>> pat.search( adefAbc ).group() #结尾是a、b、c中的任意一个的一次或则多次,贪婪:匹配
多个
>>> bc
>>> pat.findall( adefAbc )
>>> [ bc ]

“A”:匹配字符串开头

1
2
3
4
5
>>> pat = re.compile( A[abc]+ )
>>> pat.findall( cbadefab )
>>> [ cba ]
>>> pat.search( cbadefab ).group()
>>> cba

“Z”:匹配字符串结尾

1
2
3
4
5
>>> pat = re.compile( [abc]+Z )
>>> pat.search( cbadefab ).group()
>>> ab
>>> pat.findall( cbadefab )
>>> [ ab ]

分组
(…):分组匹配,从左到右,每遇到一个 ( 编号+1,分组后面可加数量词

1
2
3
4
5
6
7
8
9
10
11
12
>>> pat=re.compile(r (a)w(c) ) #w:单个的数字或字母 [A-Za-z0-9]
>>> pat.match( abcdef ).group()
>>> abc
>>> pat=re.compile( (a)b(c) ) #分2组,匿名分组
>>> pat.match( abcdef ).group() #默认返回匹配的字符串
>>> abc
>>> pat.match( abcdef ).group(1) #取分组1,适用于search
>>> a
>>> pat.match( abcdef ).group(2) #取分组2,适用于search
>>> c
>>> pat.match( abcdef ).groups() #取所有分组,元组形式返回
>>> ("a","c")

:引用编号为的分组匹配到的字符串

1
2
3
>>> pat=re.compile(r www.(.*)..{3} )
>>> pat.match( www.dxy.com ).group(1)
>>> dxy

“(?P…)” :在模式里面用()来表示分组(命名分组),适用于提取目标字符串中的某一些部位。

1
2
3
4
5
6
7
8
9
10
11
>>> pat=re.compile(r (?P<K>a)w(c) ) #分2组:命名分组+匿名分组
>>> pat.search( abcdef ).groups() #取所有分组,元组形式返回
>>> ("a","c")
>>> pat.search( abcdef ).group(1) #取分组1,适用于match
>>> a
>>> pat.search( abcdef ).group(2) #取分组2,适用于match
>>> c
>>> pat.search( abcdef ).group() #默认返回匹配的字符串
>>> abc
>>> pat.search( abcdef ).groupdict() #命名分组可以返回一个字典【专有】,匿名分组也没有
>>> { K :"a"}

“(?P=name)”:引用别名为的分组匹配到的串

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>>> pat=re.compile(r (?P<K>a)w(c)(?P=K) ) #(?P=K)引用分组1的值,就是a
>>> pat.search( abcdef ).group() #匹配不到,因为完整 awca ,模式的第4位是
a
>>> Traceback (most recent call last):
>>> File "<stdin>", line 1, in <module>
>>> AttributeError: NoneType object has no attribute group
>>> pat.search( abcadef ).group() #匹配到,模式的第4位和组1一样,值是c
>>> abca
>>> pat.search( abcadef ).groups()
>>> ("a","c")
>>> pat.search( abcadef ).group(1)
>>> a
>>> pat.search( abcadef ).group(2)
>>> c

“” :引用分组编号匹配:

1
2
3
4
5
6
7
8
9
10
11
12
13
>>> pat=re.compile(r (?P<K>a)w(c)(?P=K) ) #引用分组2的值,就是c
>>> pat.findall( Aabcadef ) #匹配不到,因为完整 awcac ,模式的第5位是c
>>> []
>>> pat.findall( Aabcacdef ) #匹配到,模式的第5位和组2一样,值是c
>>> [("a","c")]
>>> pat.search( Aabcacdef ).groups()
>>> ("a","c")
>>> pat.search( Aabcacdef ).group()
>>> abcac
>>> pat.search( Aabcacdef ).group(1)
>>> a
>>> pat.search( Aabcacdef ).group(2)
>>> c

特殊构造

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(?:…) (…)不分组版本,用于使用 | 或者后接数量词
(?iLmsux) iLmsux的每个字符代表一个匹配模式,只能用在正则表达式的开头,可选多个
(?#…) #号后的内容将作为注释
(?=…) 之后的字符串内容需要匹配表达式才能成功匹配
(?!…) 之后的字符串不匹配表达式才能成功
(?(?(?(id/name) yes |no) 如果编号为id/名字为name的组匹配到字符串,则需要匹配yes,否则匹配
no,no可以省略
```
“(?:…)” :()里面有?:表示该()不是分组
```python
>>> pat=re.compile(r a(?:bc) )
>>> pat.findall("abc")
>>> ["abc"]
>>> pat.match("abc").groups()

“(?=…)”:匹配…表达式,返回。对后进行匹配,总是对后面进行匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
>>> pat=re.compile(r w(?=d) ) #匹配表达式d,返回数字的前一位,w:单词字符[A-Za-z0-9]
>>> pat.findall( abc1 def1 xyz1 )
>>> ["c","f", z ]
>>> pat.findall( zhoujy20130628hangzhou ) #匹配数字的前一位,列表返回
>>> [ y , 2 , 0 , 1 , 3 , 0 , 6 , 2 ]
>>> pat=re.compile(r w+(?=d) )
>>> pat.findall( abc1,def1,xyz1 ) #匹配最末数字的前字符串,列表返回
>>> ["abc", def , xyz ]
>>> pat.findall( abc21,def31,xyz41 )
>>> ["abc2", def3 , xyz4 ]
>>> pat.findall( zhoujy20130628hangzhou )
>>> [ zhoujy2013062 ]
>>> pat=re.compile(r [A-Za-z]+(?=d) ) #[A-Za-z],匹配字母,可以用其他的正则方法
>>> pat.findall( zhoujy20130628hangzhou123 ) #匹配后面带有数字的字符串,列表返回
>>> [ zhoujy , hangzhou ]
>>> pat.findall( abc21,def31,xyz41 )
>>> ["abc", def , xyz ]

“(?!…)” 不匹配…表达式,返回。对后进行匹配

1
2
3
4
5
6
>>> pat=re.compile(r [A-Za-z]+(?!d) ) #[A-Za-z],匹配字母,可以用其他的正则方法
>>> pat.findall( zhoujy20130628hangzhou123,12,binjiang310 ) #匹配后面不是数字的字符
串,列表返回
>>> [ zhouj , hangzho , binjian ]
>>> pat.findall( abc21,def31,xyz41 )
>>> [ ab , de , xy ]

“(?<=…)”:匹配…表达式,返回。对前进行匹配,总是对前面进行匹配

1
2
3
4
5
6
7
>>> pat=re.compile(r (?<=d)[A-Za-z]+ ) #匹配前面是数字的字母
>>> pat.findall( abc21,def31,xyz41 )
>>> []
>>> pat.findall( 1abc21,2def31,3xyz41 )
>>> ["abc", def , xyz ]
>>> pat.findall( zhoujy20130628hangzhou123,12,binjiang310 )
>>> [ hangzhou ]

“(?<!…)”:不匹配…表达式,返回。对前进行匹配,总是对前面进行匹配

1
2
3
4
5
>>> pat=re.compile(r (?<!d)[A-Za-z]+ ) #匹配前面不是数字的字母
>>> pat.findall( abc21,def31,xyz41 )
>>> ["abc", def , xyz ]
>>> pat.findall( zhoujy20130628hangzhou123,12,binjiang310 )
>>> [ zhoujy , angzhou , binjiang ]

“(?(id/name) yes |no)”: 组是否匹配,匹配返回

1
2
3
4
5
6
7
8
9
10
11
12
>>> pat=re.compile(r a(d)?bc(?(1)d) ) #no省略了,完整的是adbcd ==> a2bc3,总共5位,
2位是可有可无的数字,第5为是数字
>>> pat.findall( abc9 ) #返回组1,但第2位(组1)没有,即返回了
>>> [ ]
>>> pat.findall( a8bc9 ) #完整的模式,返回组1
>>> [ 8 ]
>>> pat.match( a8bc9 ).group()
>>> a8bc9
>>> pat.match( a8bc9 ).group(1)
>>> 8
>>> pat.findall( a8bc ) #第5位不存在,则没有匹配到
>>> []

“(?iLmsux)”:这里就介绍下 i 参数:大小写区分匹配

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
>>> pat=re.compile(r"abc")
>>> pat.findall("abc")
>>> ["abc"]
>>> pat.findall("ABC")
>>> []
>>> pat=re.compile(r (?i)abc ) #(?i) 不区分大小写
>>> pat.findall("ABC")
>>> ["ABC"]
>>> pat.findall("abc")
>>> ["abc"]
>>> pat.findall("aBc")
>>> ["aBc"]
>>> pat.findall("aBC")
>>> ["aBC"]
>>> pat=re.compile(r"abc",re.I) #re.I 作为参数使用,推荐
>>> pat.findall("aBC")
>>> ["aBC"]
>>> pat.findall("abc")
>>> ["abc"]
>>> pat.findall("ABC")
>>> ["ABC"]

提取数据

使用正则表达式对需要的项进行处理。

保存数据

Excel 表存储

用 python 库xlwt将抽取的数据写入 Excel 表格

xlwt 模块

简单使用 xlwt

1
2
3
4
5
import xlwt #导入模块
workbook = xlwt.Workbook(encoding='utf-8') #创建workbook 对象
worksheet = workbook.add_sheet('sheet1') #创建工作表sheet
worksheet.write(0, 0, 'hello') #往表中写内容,第一各参数行,第二个参数列,第三个参数内容
workbook.save('students.xls') #保存表为students.xls

例子:

将九九乘法表显示在表格中,每个单元格 1 个公式

1
2
3
4
5
6
7
workbook = xlwt.Workbook(encoding='utf-8') #创建workbook 对象
worksheet = workbook.add_sheet('sheet1') #创建工作表sheet
for i in range(0,9):
for j in range(0,i+1):
worksheet.write(i, j, "%d * %d = %d"%(i+1,j+1,(i+1)*(j+1)))

workbook.save('students.xls') #保存表为students.xls

数据库存储

可以参考菜鸟教程:

Python 和 SQLite

SQLite 和 SQL 语句

1.引入 sqlite3 库

import sqlite3

2.初始化数据库

demo:

1
2
3
import sqlite3
conn = sqlite3.connect('test.db')
print ("Opened database successfully")

3.创建数据表

1
2
3
4
5
6
7
8
9
10
11
12
13
import sqlite3
conn = sqlite3.connect('test.db')
print ("Opened database successfully")
c = conn.cursor()
c.execute('''"CREATE TABLE COMPANY
(ID INT PRIMARY KEY NOT NULL,
NAME TEXT NOT NULL,
AGE INT NOT NULL,
ADDRESS CHAR(50),
SALARY REAL);''')
print ("Table created successfully")
conn.commit()
conn.close()

4.数据库存储

插入数据:insert 操作

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import sqlite3
conn = sqlite3.connect('test.db')
c = conn.cursor()
print ("Opened database successfully")
c.execute("INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) \
VALUES (1, 'Paul', 32, 'California', 20000.00 )");
c.execute("INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) \
VALUES (2, 'Allen', 25, 'Texas', 15000.00 )");
c.execute("INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) \
VALUES (3, 'Teddy', 23, 'Norway', 20000.00 )");
c.execute("INSERT INTO COMPANY (ID,NAME,AGE,ADDRESS,SALARY) \
VALUES (4, 'Mark', 25, 'Rich-Mond ', 65000.00 )");
conn.commit()
print ("Records created successfully")
conn.close()

5.数据库查询

select 操作:

下面的 Python 程序显示了如何从前面创建的 COMPANY 表中获取并显示记录:

1
2
3
4
5
6
7
8
9
10
11
12
import sqlite3
conn = sqlite3.connect('test.db')
c = conn.cursor()
print ("Opened database successfully")
cursor = c.execute("SELECT id, name, address, salary from COMPANY")
for row in cursor:
print ("ID = ", row[0])
print ("NAME = ", row[1])
print ("ADDRESS = ", row[2])
print ("SALARY = ", row[3], "\n")
print("Operation done successfully")
conn.close()

6.修改操作:update(略)

7.删除操作:delete(略)


技巧合集

将 excel 文件导入 mysql 数据库

参考文章:将 Excel 文件导入到 Navicat for MySQL 数据库方法-百度经验

python3 中 url 不可以包含中文,需要编码

1
2
3
4
5
from urllib import parse

key = '黄王不分'
encoded_url = 'https://blog.harriswong.top/'+urllib.parse.quote(key)
print(encoded_url)