Featured image of post [2024重置] Python爬虫看这篇就够了01 httpx+bs4+正则 基础用法

[2024重置] Python爬虫看这篇就够了01 httpx+bs4+正则 基础用法

python3 httpx + beautifulsoup4 + 正则表达式, select元素, 获取属性值get(), 获取文本getText()

经过 2 个 Python 项目的洗礼(加一起 2k 行代码了), 现在再回过头看以前的文章, 实在看不下去, 所以现在有空就重制一下该系列

环境:python3.11

pip install beautifulsoup4 httpx

httpx 的进阶用法 异步/控制并发数/多线程/线程池 的爬虫方法我们放在下一篇文章讲解, 本篇文章主要讲基本使用

httpx 基本用法

# 是否启用代理
proxy_status = True

# 请求头
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/535.36",
    "referer": "https://movie.douban.com/"
}

# cookies
cookies = {
    "token": "123"
}

# 代理
proxies = {
    'http://': 'http://127.0.0.1:7778',
    'https://': 'http://127.0.0.1:7778',
}

# 判断是否代理
if not proxy_status:
    proxies = {}

with httpx.Client(headers=headers, proxies=proxies, cookies=cookies) as client:
    print(client.get("https://www.baidu.com/", timeout=10).status_code)

豆瓣

我们先来看看一个简单的例子,请求页面用 httpx,页面的数据分析用 bs4

  • 获取内容我建议是使用:select_one()select()
    • 里面通过 CSS 选择器 获取对象,这会方便很多。
    • 下面我们来举一个豆瓣 TOP250 的例子
import time
import httpx
from bs4 import BeautifulSoup
import atexit

start_url = "https://movie.douban.com/top250"
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/535.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/535.36",
    "referer": "https://movie.douban.com/"
}

start = time.time()
atexit.register(lambda: print('用时(秒):', time.time() - start))


def get_data(page_id):
    with httpx.Client(headers=headers) as client:
        # 获取网页信息
        r = client.get(
            f'https://movie.douban.com/top250?start={page_id}&filter=')

        # 解析页面信息
        soup = BeautifulSoup(r, 'html.parser')
        data = []

        # 获取整个页面的信息
        for i in soup.select('ol.grid_view .item'):
            _item = {'id': None, 'name': None}
            # 序号
            _item['id'] = i.select_one('.pic > em').get_text()
            # 名称
            _item['name'] = i.select_one('.info span.title').get_text()

            data.append(_item)

        return data


def main():
    finalData = []
    # 遍历
    for i in range(0, 250, 25):
        finalData.append(get_data(i))

    return finalData


data = main()
print(data)

解析

  • 相同颜色的框,代表他对应的内容。
  • 29 行,我先用 for 选择中了这 25 个列表元素
  • 然后通过遍历分别获取他们的排序和名称,最后返回结果。
    • 用时:3.5s

2024-04-20-douba

bs4

pip install beautifulsoup4
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')
  • beautifulsoup4 获取目标值一般分为两个步骤
    • 第一步: 选中目标元素 (select)
    • 第二步: 获取元素中的具体值(比如文本, 属性) 接下来我们来看下具体操作

select()

  • select(): 返回数组
    • 找不到返回空数组 []
  • select_one(): 返回一个
    • 找不到返回 None
r = """
<div id="content">
<h1>Hello World</h1>
<p class="item">
    <a href="https://www.douban.com">
        <img src="./douban.jpg" />
    </a>
</p>
</div>
"""

soup = BeautifulSoup(r, 'html.parser')

# 找不到返回 []
if not soup.select("#content123"):
    print("Not Found")
else:
    print(soup.select("#content")[0])  # 返回的是数组

# 找不到返回 None
if soup.select_one("#content123") is None:
    print("Not Found")
else:
    print(soup.select_one("#content"))

这样我们就已经选中元素了, 接下来我们来看看如何获取具体的属性/文本

获取属性值

提醒下, 我只推荐使用 get() 来获取属性, 我知道还有一种是 soup.p.a 的方式, 但是这种方式的原理是 soup.find() 容易误判并且过于长了

获取 a 标签的 href 地址

  • get()
    • 找不到返回 None
# <a href="https://www.douban.com">
data = soup.select_one("a")

if data is None:
    print("Not Found")
else:
    href = data.get("href")

    # 不是 None 时输出
    if href is not None:
        print(href)

获取文本

如何获取 h1 标签文本呢?

  • getText()
  • getText('\r\n', '<br/>')
    • <br/> 替换成 \r\n
data = soup.select_one("p")

if data is None:
    print("Not Found")
else:
    h1 = data.getText()

    # 不是 None 时输出
    if h1 is not None:
        print(h1)

正则

补充下正则的用法, 在格式化数据时非常有用

  • .compile(pattern, flags=0): 编译正则表达式
    • 使用 compile 函数进行预编译,可以提升程序的执行性能
  • .match(pattern, str, flags=0): 成功就返回匹配的对象, 失败则返回 None
    • 用来截取文本
  • 使用 group() 或者 groups() 获取结果, 不存在会返回 None

下面这个方法我们用于 获取时间, 我使用了 () 原子组因此可以直接获取

r = r"该文章是由 ABC 用户于 2024-01-01 12:30 发表"

# 找不到返回 None
data = re.compile('.*(\d{4}-\d{1,2}-\d{1,2}.*\d{1,2}:\d{1,2}).*').match(r)

if data is None:
    print("不存在")
else:
    # group: 2024-01-01 12:30
    print(data.group(1))
    # 数组也可以, 结果是一样的
    print(data[1])

    # groups: ('2024-01-01 12:30',)
    print(data.groups())
  • 接下来介绍下 flags=0 怎么写, 一般不用管
    • re.I: 区分大小写
    • re.S: 匹配任意字符, 包括换行
    • re.M: 多行模式
    • re.ASCII: 使得特殊字符类 \w, \W, \b, \B, \d, \D, \s, \S 只匹配 ASCII 字符。
    • re.U: 使得特殊字符类 \w, \W, \b, \B, \d, \D, \s, \S 匹配 Unicode 字符。
    • re.X: 忽略正则表达式中的空白和注释。

最后

bs4 其实就这么点知识, 只要你会通过 css 样式去选择元素就不难, 爬虫主要难点不在这, 在线程处理, 以及异常处理, 后面会说

基本一个合格的爬虫脚本, 都是要经过好几个版本的迭代的, 因为你无法预测所有未知的情况.

Licensed under CC BY-NC-SA 4.0
本博客已稳定运行
发表了53篇文章 · 总计28.17k字
使用 Hugo 构建
主题 StackJimmy 设计