正则表达式语法
元字符
元字符是具有固定含义的特殊符号
| . | 匹配除了换行符以外的任何字符 |
| ——– | —————- |
| \w | 匹配字符或数字或下划线 |
| \s | 匹配空白字符 |
| \d | 匹配数字 |
| \n | 匹配换行符 |
| \t | 匹配制表符 |
| | 匹配字符串的开始 |
| $ | 匹配字符串结尾 |
| \W | 匹配非字母或数字或下划线 |
| \D | 匹配非数字 |
| \S | 匹配非空白符 |
| a\|b | 匹配字符 a 或者字符 b |
| () | 匹配括号内的表达式,也表达一个组 |
| […] | 匹配字符组中的字符 |
| […] | 匹配除了字符组中字符的所有字符 |
量词
量词是控制前面的元字符出现的次数
| * | 重复 0 次或更多次 |
| —– | ———– |
| + | 重复 1 次或更多次 |
| ? | 重复 0 次或一次 |
| {n} | 重复 n 次 |
| {n,} | 重复 n 次或更多次 |
| {n,m} | 重复 n 次到 m 次 |
匹配原则
- 贪婪匹配
.*
:尽可能多的去匹配结果; - 惰性匹配
.*?
:尽可能少的去匹配结果。
str: 玩儿吃鸡游戏, 晚上一起上游戏, 干嘛呢? 打游戏啊
reg: 玩儿.*?游戏
此时匹配的是: 玩儿吃鸡游戏
reg: 玩儿.*游戏
此时匹配的是: 玩儿吃鸡游戏, 晚上一起上游戏, 干嘛呢? 打游戏
str: <div>胡辣汤</div>
reg: <.*>
结果: <div>胡辣汤</div>
str: <div>胡辣汤</div>
reg: <.*?>
结果:
<div>
</div>
str: <div class="abc"><div>胡辣汤</div><div>饭团</div></div>
reg: <div>.*?</div>
结果:
<div>胡辣汤</div>
<div>饭团</div>
re 模块
findall
查找所有,返回 list。
lst = re.findall("m", "mai le fo len, mai ni mei!")
print(lst) # ['m', 'm', 'm']
lst = re.findall(r"\d+", "5点之前,给我500万")
print(lst) # ['5', '5000']
search
进行匹配,如果匹配到了就返回匹配到的第一个结果,如果没有匹配上,则返回 None。
ret = re.search(r'\d', '5点之前,给我500万')
print(ret) # 5
match
只从字符串开头匹配,相当于给表达式加了 ^
ret = re.match('a', 'abc').group()
print(ret) # a
finditer
和 findall
差不多,只不过这时返回迭代器,相对来说比较省内存
ite = re.finditer("m", "mai le fo len, mai ni mei!")
for item in ite:
print(item.group()) # 依然需要分组
compile()
可以预加载一个表达式,方便后面使用
import re
import traceback
obj_li = re.compile(r"<li>.*?</li>", re.S)
obj_rank = re.compile(r'<em class="">(?P<rank>\d+)</em>')
obj_name = re.compile(r'<span class="title">(?P<name>.*?)</span>')
obj_drc = re.compile(r'导演: (?P<drc>.*?) ')
obj_role = re.compile(r'主演: (?P<role>.*?)<br>')
log = open("./log", 'w', encoding='utf-8')
f = open("./top250.html", 'r', encoding='utf-8')
content = f.read()
f.close()
ite_li = obj_li.finditer(content)
for item in ite_li:
try:
str = item.group()
rank = obj_rank.search(str).group("rank")
name = obj_name.search(str).group("name")
drc = obj_drc.search(str).group("drc")
role = obj_role.search(str).group("role")
print(rank, name, "导演: ", drc, role)
except Exception as e:
log.write(traceback.format_exc())
log.write('\n')
bs4
2.1 HTML 基本结构
HTML(Hyper Text Markup Language)超文本标记语言, 是我们编写网页的最基本也是最核心的一种语言. 其语法规则就是用不同的标签对网页上的内容进行标记, 从而使网页显示出不同的展示效果.
<h1>
我爱你
</h1>
上述代码的含义是在页面中显示"我爱你"三个字, 但是我爱你三个字被”
“和”
“标记了. 白话就是被括起来了. 被 H 1 这个标签括起来了. 这个时候. 浏览器在展示的时候就会让我爱你变粗变大. 俗称标题, 所以 HTML 的语法就是用类似这样的标签对页面内容进行标记. 不同的标签表现出来的效果也是不一样的.h1: 一级标题
h2: 二级标题
p: 段落
font: 字体(被废弃了, 但能用)
body: 主体
这里只是给小白们简单科普一下, 其实 HTML 标签还有很多很多的. 我们不需要一一列举(这是爬虫课, 不是前端课).
OK~ 标签我们明白了, 接下来就是属性了.
<h1>
我爱你
</h1>
<h1 align='right'>
我爱你妹
</h1>
有意思了. 我们发现在标签中还可以给出 xxx=xxx 这样的东西. 那么它又是什么呢? 又该如何解读呢?
首先, 这两个标签都是 h 1 标签, 都是一级标题, 但是下面这个会显示在右边. 也就是说, 通过 xxx=xxx 这种形式对 h 1 标签进一步的说明了. 那么这种语法在 html 中被称为标签的属性. 并且属性可以有很多个. 例如:
<body text="green" bgcolor="#eee">
你看我的颜色. 贼健康
</body>
总结, html
语法:
<标签 属性1="值" 属性2="值">
被标记的内容
</标签>
或
<标签 属性1="值" 属性2="值"/>
对于语法层面, 我们知道这么多就够了. 大多数情况下, 我们并不用关心 div
和 span
有什么区别. 也不用关心 section
是什么. 但是有几个标签. 我们是必须要知道的. 因为未来, 我们会高密度的和这几个标签做斗争.
- a 超链接
- img 图片
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<a href="http://www.baidu.com">我是百度</a>
<a href="myself.html">我是自己</a>
<a href="dddd/myself.html">我是自己</a>
<img src="https://mmbiz.qpic.cn/mmbiz/ZKGHmo3MibQUhB9O7E0b05wBDuP1beR86kcXtFsPUZVnanPW2Sjmwiaia0ibICJt4Q8Q7RDyF3bthpciaBuOwcgEWZg/0"/>
</body>
</html>
2.2 CSS 选择器
CSS
全称层叠样式表(Cascading Style Sheets), 主要用来定义页面内容展示效果的一门语言.
HTML
: 页面骨架. 素颜
CSS
: 页面效果美化+布局. 美妆+滤镜
2.2.1. css 语法规则:
通过
style
属性来编写样式通过
style
标签中定义选择器
. 然后使用选择器的来选择页面上的元素, 添加样式在
css
文件中编写样式, 通过 link 引入该文件
2.2.2. css 选择器(重点)
1. id选择器 #id值
2. 标签选择器 标签
3. 类选择器 .
4. 选择器分组 ,
5. 后代选择器 空格
6. 子选择器 父 > 子
7. 属性选择器 [属性=值]
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
/*标签选择器, 选择页面上的标签, 添加样式*/
div{
width:400px;
height:300px;
border: 1px solid red;
float: left;
font-size: 1cm;
}
/*id选择器, 根据页面上的id值.选择标签*/
#jay{
color:green;
}
/*类选择器, 根据页面上的class值.选择标签*/
.wlh{
color:yellowgreen;
}
/*类选择器, 根据页面上的class值.选择标签*/
/*
解读: 寻找class是wlh的p标签
<p class='wlh'></p>
*/
p.wlh{
color:lightseagreen;
}
/*标签选择器, 根据页面上的标签名.选择标签*/
article{
color:darkgreen;
}
/*
选择器分组, 符合以下条件的选择器被选择
解读: 多个选择器一起生效
*/
.zu1,.zu2{
color: orange;
}
/*
后代选择器, 符合该结构的页面内容被选择
*/
section span{
color: springgreen;
}
/*
子选择器, 符合该结构的父子关系的被选择
*/
strong > span{
color: lawngreen;
}
/*
符合 属性=值 的标签被选择
解读: code标签中, abc='haha'的被添加样式
*/
code[abc='haha']{
color:mediumspringgreen;
}
</style>
</head>
<body>
<div>
id选择器
<span id="jay">我是周杰伦, 我也是id选择器</span>
</div>
<div>类选择器
<span class="wlh">我是王力宏, 我也是类选择器</span>
<section class="wlh">我是一样也是王力宏, 我也是类选择器</section>
<p class="wlh">我还没那么绿</p>
</div>
<div>标签选择器
<article>我也是article, 我是分组选择器</article>
</div>
<div>选择器分组
<article class="zu1">我是article, 我是标签选择器</article>
<section class="zu2">我是section, 我是标签选择器</section>
</div>
<div>后代选择器
<section><span>一样是吃</span></section>
<span>一样是吃</span>
</div>
<div>子选择器
<strong><span>一样是喝</span></strong>
<strong><i><span>一样是喝</span></i></strong>
</div>
<div>属性选择器
<code abc="haha">代码</code>
<code>代码</code>
<code abc="haha">代码</code>
<code>带带吗</code>
</div>
</body>
</html>
注意, 在class里可能会有多个值. 表示多个选择器
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
<style>
div{
width: 200px;
height: 200px;
border: 1px solid red;
}
.jay{
color:red;
}
.wlh{
background:pink;
}
</style>
</head>
<body>
<!-- 这里是两个选择器的意思. -->
<div class="jay wlh">吃饭了么</div>
<div class="jay">吃多了吧</div>
</body>
</html>
2.3 bs 4 解析
有了这些基础了. 我们尝试着学学这个叫 bs4
的东西. bs4
的逻辑很简单. 直接用标签和属性来选择页面上的标签
bs 4 是一个第三方模块. 需要单独安装
pip install bs4 # BeautifulSoup
2.3.1 通用查询方案
关于 bs4
, 本质上我们知道两个东西就好, 一个是 find
,另一个是 find_all
, 从名字上看. 一个是 查找一个
, 另一个是 查找所有
.
- find, 在页面中查找一个结果, 找到了就返回
- find_all, 在页面中查找一堆结果. 找完了才返回
这两个功能拥有相同的参数结构. 学一个即可
find(标签, attrs={属性:值})
上个案例试试
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
url = "https://desk.zol.com.cn/pc/"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36"
}
# 发送请求, 获取页面源代码
resp = requests.get(url, headers=headers)
resp.encoding = 'gbk' # 设置一下字符集
# 1. 创建BeautifulSoup的对象
main_soup = BeautifulSoup(resp.text, "html.parser")
# 2. 找到超链接, 请注意.这里面的属性拼接,多少会和页面稍微有一些细微的差别.
a_list = main_soup.find("ul", attrs={"class": "pic-list2 clearfix"}).find_all("a")
# 3. 循环出每一个超链接
for a in a_list:
# 4.1 拿到href, 也就是子页面的url
href = a.get("href")
# 4.2 获取超链接中的文本信息
content = a.text
print("没啥用,只是给你演示如何获取文本", content)
# 5. 剔除特殊项
if href.endswith(".exe"): # 垃圾客户端. 坑我
continue
# 6. 域名拼接
href = urljoin(url, href)
# 7. 剩下的就是套娃了
child_resp = requests.get(href, headers=headers)
child_resp.encoding = 'gbk'
child_soup = BeautifulSoup(child_resp.text, "html.parser")
# print(child_resp.text) # 适当的打印,可以帮助你调BUG
img = child_soup.find("img", attrs={"id": "bigImg"})
img_src = img.get("src")
# 下载图片
img_resp = requests.get(img_src, headers=headers)
file_name = img_src.split("/")[-1]
with open(file_name, mode="wb") as f:
f.write(img_resp.content)
print("下载完一张图片了")
2.3.2 利用 css 选择器来获取页面内容
关于选择器. 这里我们讲两个功能
- select_one(选择器) 使用
选择器
获取 html 文档中的标签, 拿一个 - select(选择器) 使用
选择器
获取 html 文档中的标签, 拿一堆
import requests
from bs4 import BeautifulSoup
from urllib.parse import urljoin
url = "https://desk.zol.com.cn/pc/"
headers = {
"user-agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.82 Safari/537.36"
}
# 发送请求, 获取页面源代码
resp = requests.get(url, headers=headers)
resp.encoding = 'gbk' # 设置一下字符集
# 1. 创建BeautifulSoup的对象
main_soup = BeautifulSoup(resp.text, "html.parser")
# 2. 找到超链接, 请注意.这里面的属性拼接,多少会和页面稍微有一些细微的差别.
a_list = main_soup.select("ul.pic-list2 a")
# 3. 循环出每一个超链接
for a in a_list:
# 4.1 拿到href, 也就是子页面的url
href = a.get("href")
# 4.2 获取超链接中的文本信息
content = a.text
print("没啥用,只是给你演示如何获取文本", content)
# 5. 剔除特殊项
if href.endswith(".exe"): # 垃圾客户端. 坑我
continue
# 6. 域名拼接
href = urljoin(url, href)
# 7. 剩下的就是套娃了
child_resp = requests.get(href, headers=headers)
child_resp.encoding = 'gbk'
child_soup = BeautifulSoup(child_resp.text, "html.parser")
# print(child_resp.text) # 适当的打印,可以帮助你调BUG
img = child_soup.select_one("#bigImg")
img_src = img.get("src")
# 下载图片
img_resp = requests.get(img_src, headers=headers)
file_name = img_src.split("/")[-1]
with open(file_name, mode="wb") as f:
f.write(img_resp.content)
print("下载完一张图片了")
xpath
xpath
是一种非常简单好用的页面提取方案,使用去需要安装 lxml 模块:
pip install lxml
xpath 语法
示例 html 代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Title</title>
</head>
<body>
<div>
<p>一个很厉害的人</p>
<ol>
<li id="10086">周大强</li>
<li id="10010">周芷若</li>
<li class="joy">周杰伦</li>
<li class="jolin">蔡依林</li>
<ol>
<li>阿信</li>
<li>信</li>
<li>信不信</li>
</ol>
</ol>
</div>
<hr />
<ul>
<li><a href="http://www.baidu.com">百度</a></li>
<li><a href="http://www.google.com">谷歌</a></li>
<li><a href="http://www.sogou.com">搜狗</a></li>
</ul>
<ol>
<li><a href="feiji">飞机</a></li>
<li><a href="dapao">大炮</a></li>
<li><a href="huoche">火车</a></li>
</ol>
<div class="job">李嘉诚</div>
<div class="common">胡辣汤</div>
</body>
</html>
xpath 简单实用
from lxml import etree
# 加载HTML
f = open("xpath测试.html", mode='r', encoding='utf-8')
page_source = f.read()
# 和bs4一样, 把HTML交给etree
hm = etree.HTML(page_source) # type:etree._Element
# xpath各种用法
# 节点: 每个HTML标签叫节点
# 最外层节点: 根节点
# 内层节点: 子节点
# 父子节点: <爹><子></子></爹>
html = hm.xpath("/html") # /根
print(html) # 看一眼标签名(测试用, 不用记住)
body = hm.xpath("/html/body") # 第二个/表示子节点
print(body)
# 接下来这句话请记住, xpath提取到的内容不论多少, 都会返回列表
p = hm.xpath("/html/body/div/p/text()") # 想要p里面的文本
print(p) # 列表
print(p[0]) # 要么取0.
print("".join(p)) # 要么用join()进行合并.
# 如果页面结构非常复杂. 这样一层一层数下来. 过年了
# xpath我们还可以用相对定位
p = hm.xpath("//p/text()") # // 表示在页面任意位置找
print(p) # 依然有效
# 我想找到 `周大强`, `周芷若`,`周杰伦`,`周大强`,`蔡依林`
li = hm.xpath("//div/ol/li/text()")
print(li)
# 我想找到 `一个很厉害的人`后面ol中所有的文本
li = hm.xpath("//div/ol//text()") # 第二个//表示所有
# 请注意. 这里多了很多空白,一般我们提取一篇文章的时候,会用这种.
# 结合字符串各种操作. 这些东西应该难不倒各位.
print(li)
print("".join(li).replace(" ", "").replace("\n", ""))
xpath 进阶
from lxml import etree
# 加载HTML
f = open("xpath测试.html", mode='r', encoding='utf-8')
page_source = f.read()
# 和bs4一样, 把HTML交给etree
hm = etree.HTML(page_source) # type:etree._Element
# 重点:根据位置数元素
# 我想要`周芷若`, 分析角度: 它是ol里第二个li
li = hm.xpath("//ol/li[2]/text()")
print(li) # 这里莫名其妙带出了`信`, 请思考为什么? 请思考怎么干掉`信`
# 我想单独找`信`聊聊
li = hm.xpath("//ol/ol/li[2]/text()")
print(li)
# 重点:根据属性筛选元素
# 我想要id=10086的li标签中的内容0
li = hm.xpath("//li[@id='10086']/text()")
print(li)
# 我想要class是joy的内容
li = hm.xpath("//*[@class='joy']/text()")
print(li)
# 我想要有class的li的内容
li = hm.xpath("//*[@class]/text()")
print(li)
# 我想和`啊信`,`信`,`信不信`单独聊聊
li_list = hm.xpath("//ol/ol/li")
for li in li_list:
print(li.xpath("./text()")) # ./表示当前节点, 也就是li
# 提取`百度`, 谷歌, 搜狗的超链接href属性
li_list = hm.xpath("//ul/li")
for li in li_list:
print(li.xpath("./a/text()")) # ./表示当前节点, 也就是li
print(li.xpath("./a/@href")) # @href 表示提取属性
# 我想要`火车`
li = hm.xpath("//body/ol/li[last()]/a/text()") # last() 拿到最后一个
print(li)
f.close()
评论