正则表达式语法

元字符

元字符是具有固定含义的特殊符号

| . | 匹配除了换行符以外的任何字符 |
| ——– | —————- |
| \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>.*?)&nbsp;')  
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="值"/>

对于语法层面, 我们知道这么多就够了. 大多数情况下, 我们并不用关心 divspan 有什么区别. 也不用关心 section 是什么. 但是有几个标签. 我们是必须要知道的. 因为未来, 我们会高密度的和这几个标签做斗争.

  1. a 超链接
  2. 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 语法规则:

  1. 通过 style 属性来编写样式

  2. 通过 style 标签中定义 选择器. 然后使用选择器的来选择页面上的元素, 添加样式

  3. 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, 从名字上看. 一个是 查找一个, 另一个是 查找所有.

  1. find, 在页面中查找一个结果, 找到了就返回
  2. 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 选择器来获取页面内容

关于选择器. 这里我们讲两个功能

  1. select_one(选择器) 使用 选择器 获取 html 文档中的标签, 拿一个
  2. 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()