一文了解 Python 爬虫

364 阅读15分钟

一文快速了解 Kotlin 与 Python 的区别 这篇文章中,我们比较了 Kotlin 和 Python 的区别。这篇文章就将介绍 Python 在爬虫方面的应用。

网络爬虫的种类

爬虫其实就是一段计算机程序,其核心作用就是获取网页内容并解析。网络爬虫按照系统结构和实现技术,大致可以分为以下几种类型:

  • 通用网络爬虫:又称搜索引擎爬虫,这是最常见的一种爬虫类型,主要用于搜索引擎。
  • 聚焦网络爬虫:又称主题网络爬虫是指选择性地爬行那些与预先定义好的主体页面相关的网络爬虫
  • 增量式网络爬虫:对已爬取的网页进行增量式更新或只爬行新产生的或者已经发生变化网页的爬虫
  • 深层网络爬虫:深层网络指那些大部分内容不能通过静态链接获取、隐藏在搜索表单后的,只有用户提交一些关键词才能获得的 Web 页面。深层网络爬虫主要用于爬取深层页面,其核心部分为表单填写,包含基于领域知识的表单填写和基于网页结构分析的表单填写两种类型

开发爬虫

正则表达式

在爬虫的开发中,需要把有用的信息从一大段文本中提取出来。而正则表达式就是提取信息的方法之一,而且在某些情况下,只有使用正则表达式才能达到目的。因此学好正则表达式,是开发爬虫的第一步。

  • 点号 .

一个点号可以代替除了换行符以外的任何一个字符,包括但不限于英文字母、数字、汉字、英文标点符号和中文标点符号。示例如下:

kin...me 可以代表如下字符串:

kingname
kinabcme
kin123me
kin我是谁me
kin嗨你好me
kin"m"me
  • 星号

一个星号可以表示它前面的一个子表达式(普通字符、另一个或几个正则表达式符号)0次到无限次。示例如下

如果快乐你就笑哈* 可以代表如下字符串

如果快乐你就笑哈
如果快乐你就笑哈哈
如果快乐你就笑哈哈哈哈
如果快乐你就笑哈哈哈哈哈哈哈哈哈
  • 问号 ?

问号表示它前面的子表达式0次或者1次。注意,这里的问号是英文问号。示例如下:

笑起来哈?。 可以代表如下字符串

笑起来。
笑起来哈。

问号最大的用处是与点号和星号配合起来使用,构成 .*? 。通过正则表达式来提取信息的时候,用到最多的也是这个组合。

.*.*? 的区别:.* 会获取最长的满足条件的字符串。而 .*? 会获取最短的能满足条件的字符串。

  • 反斜杠 \

反斜杠可以把特殊符号变成普通符号,还可以把普通符号变成特殊符号。比如 \* 表示是一个普通的 *;\n 表示换行。常见的转义字符如下所示:

image.png

  • 数字 \d

正则表达式里面使用“\d”来表示一位数字。如果要提取两个数字,可以使用 \d\d;如果要提取3个数字,可以使用\d\d\d。但是如果不知道这个数有多少位,可以使用 *号来表示一个任意位数的数字,即 \d*。示例如下:

是\d*,请记住它。 可以代表如下字符串

是123455677,请记住它。
是1,请记住它。
是66666,请记住它。
  • 小括号 ()

小括号可以把括号里面的内容提取出来。示例如下:

:(.*? )你 表达式可以提取下面字符串中的密码为: 12345abcde

我的密码是:12345abcde你帮我记住。

在Python中使用正则表达式

Python的正则表达式模块名字为re,该模块中一般使用 findallsearch方法来做正则表达式匹配的操作。以下是它们的使用方法及示例介绍:

re.findall

re.findall 的函数原型为 re.findall(pattern, string, flags=0),其中pattern是要匹配的正则表达式模式,string是待匹配的字符串,flags是可选的修饰标志(比如re.I表示忽略大小写等)。该方法会在整个string中查找所有与pattern匹配的内容,并以列表形式返回所有匹配结果。如果没有匹配到,则返回空列表。代码示例如下:

import re

text = "hello 123 world 456"
pattern = r"\d+"  # 匹配数字
result = re.findall(pattern, text)
print(result)  
# 输出: ['123', '456'],找到了文本中所有连续的数字并返回列表

在 Python 中,字符串前面的r表示原始字符串。当使用原始字符串时,Python 会将反斜杠\视为普通字符,而不会对其进行转义解释。

re.search

re.search 的函数原型为 re.search(pattern, string, flags=0),同样pattern为正则表达式模式,string是目标字符串,flags为可选修饰标志。它会在string中进行搜索,只要找到第一个匹配pattern的内容就停止搜索,并返回一个匹配对象(包含匹配相关信息),如果没有找到匹配内容,则返回None。可以通过匹配对象的相关方法(如group())获取匹配到的具体内容。代码示例如下:

import re

text = "hello 123 world 456"
pattern = r"\d+"
match_obj = re.search(pattern, text)
if match_obj:
    print(match_obj.group())
# 输出: 123,只返回了第一个匹配到的数字部分,找到就停止后续搜索了

使用Python获取网页源代码

一般我们使用 requests 这个第三方库来获取网页的内容。首先我们需要使用 pip install requests 来安装三方库。

安装完成后,就可以使用 requests 来获取网页的源码了。代码示例如下:

import requests

# 发送 GET 请求
url = 'https://example.com'  # 替换为你想要获取数据的网页 URL
response = requests.get(url)

# 检查请求是否成功
if response.status_code == 200:
    # 获取网页的内容
    page_content = response.text
    print(page_content)
else:
    print(f"请求失败,状态码: {response.status_code}")

获取到网页数据后,就可以使用上面介绍的正则表达式模块来获取想要的内容。

HTML内容解析

XPath 的使用

XPath(XML Path)是一种查询语言,它能在XML(Extensible Markup Language,可扩展标记语言)和HTML的树状结构中寻找结点。用正则表达式来提取信息,经常会出现不明原因的无法提取想要内容的情况。而XPath却不一样,熟练使用XPath以后,构造不同的XPath,所需要花费的时间几乎是一样的,所以用XPath从HTML源代码中提取信息可以大大提高效率。

在Python中,为了使用XPath,需要安装一个第三方库:lxml。首先先安装 lxml 库(如果未安装),可以使用 pip install lxml 命令进行安装。然后导入 lxml 库的 etree 模块,再使用 etree.HTML() 方法将 HTML 字符串解析为可使用 XPath 操作的元素树。最后就可以使用 XPath 表达式从元素树中提取所需的信息了。代码示例如下:

from lxml import etree

# 假设这是一段 HTML 内容
html_content = """
<html>
<head>
    <title>Sample Page</title>
</head>
<body>
    <div class="container">
        <h1>Hello, World!</h1>
        <p>This is a sample paragraph.</p>
        <ul>
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 3</li>
        </ul>
    </div>
</body>
</html>
"""

# 将 HTML 内容解析为元素树
html_tree = etree.HTML(html_content)

# 使用 XPath 表达式提取元素

# 提取 title 元素的文本内容
title = html_tree.xpath('//title/text()')
print("Title:", title[0])  

# 提取 h1 元素的文本内容
h1 = html_tree.xpath('//h1/text()')
print("H1:", h1[0])  

# 提取所有 li 元素的文本内容
li_elements = html_tree.xpath('//li/text()')
print("List items:", li_elements)  

# 提取具有 class 属性为 'container' 的 div 元素中的 p 元素的文本内容
p_in_container = html_tree.xpath("//div[@class='container']/p/text()")
print("Paragraph in container:", p_in_container[0])

至于 XPath 表达式,我们可以借助Google Chrome浏览器来获取,如下图所示:

屏幕截图 2025-01-05 162440.png

Beautiful Soup4的使用

Beautiful Soup4(BS4)是Python的一个第三方库,用来从HTML和XML中提取数据。Beautiful Soup4在某些方面比XPath易懂,但是不如XPath简洁,而且由于它是使用Python开发的,因此速度比XPath慢。

首先我们需要安装 Beautiful Soup4 库(如果未安装),可使用 pip install beautifulsoup4 命令进行安装。然后就可以使用这个库来解析了。代码示例如下:

from bs4 import BeautifulSoup
import requests

# 假设这是一段 HTML 内容
html_content = """
<html>
<head>
    <title>Sample Page</title>
</head>
<body>
    <div class="container">
        <h1>Hello, World!</h1>
        <p>This is a sample paragraph.</p>
        <ul>
            <li>Item 1</li>
            <li>Item 2</li>
            <li>Item 3</li>
        </ul>
    </div>
</body>
</html>
"""

# 使用 BeautifulSoup 解析 HTML 内容
soup = BeautifulSoup(html_content, 'html.parser')

# 提取 title 元素的文本内容
title = soup.title.string
print("Title:", title)  

# 提取 h1 元素的文本内容
h1 = soup.h1.string
print("H1:", h1)  

# 提取所有 li 元素的文本内容
li_elements = [li.string for li in soup.find_all('li')]
print("List items:", li_elements)  

# 提取具有 class 属性为 'container' 的 div 元素中的 p 元素的文本内容
container_div = soup.find('div', class_='container')
p_in_container = container_div.p.string
print("Paragraph in container:", p_in_container)

数据存储

使用爬虫可以在短时间内积累大量数据,如果数据量太大,就会变得难以检索,难以管理。因此需要使用数据库技术来存储,这里推荐 MongoDBRedis

  • MongoDB 是一款基于C++开发的开源文档数据库,数据在MongoDB中以Key-Value的形式存储,就像是Python中的字典一样。具体使用看 Python MongoDB | 菜鸟教程
  • Redis 是一个基于内存的数据库,它的速度远远快过MongoDB,而且Redis比MongoDB还要简单。具体使用看 Python redis 使用介绍 | 菜鸟教程

MongoDB主要用来存放爬虫爬到的各种需要持久化保存的数据,而Redis则用来存放各种中间数据。通过减少频繁读/写MongoDB,并使用Redis来弥补MongoDB的一些不足,可以显著提高爬虫的运行效率。

如何突破常见的防爬虫手段

解决异步加载问题

AJAXAsynchronous JavaScript And XML 的首字母缩写,意为异步JavaScript与XML。使用AJAX技术,可以在不刷新网页的情况下更新网页数据。使用AJAX技术的网页,一般会使用HTML编写网页的框架。在打开网页的时候,首先加载的是这个框架。剩下的部分将会在框架加载完成以后再通过JavaScript从后台加载。

一般网页上面存在的某些文字,在源代码中却不存在的情况,绝大部分都是使用了异步加载技术。如果需要抓取采用了该技术的网页数据,有两种方法分别为:处理异步请求的数据 和 模拟浏览器

处理异步请求的数据

处理异步请求的数据就是找到真正获取数据的接口,然后请求这个接口。我们可以使用Google Chrome浏览器的开发者模式。在网页上单击右键,选择“检查”命令,然后定位到“Network”选项卡;然后单击地址栏左边的“刷新”按钮。刷新以后,可以看到“Network”选项卡下面出现了一些内容。

屏幕截图 2025-01-05 202723.png

最后尝试使用 requests 发送这个请求就可以了。

对于多次请求才能得到数据的情况,解决办法就是逐一请求,得到返回结果以后再发起下一个请求。

模拟浏览器

有一些网站在发起AJAX请求的时候,会带上特殊的字符串用于身份验证,这种字符串称为 Token。但是 token 计算对应的算法会写在网站的某一个JavaScript文件中,而网站的JavaScript一般都经过了代码混淆,要读懂需要比较深厚的JavaScript功底。如果一个网站只需要爬一次,或者对爬取速度没有要求,那么可以通过模拟浏览器的方式来爬数据。

Selenium是一个网页自动化测试工具,可以通过代码来操作网页上的各个元素。Selenium是Python中的第三方库,可以实现用Python来操作网页。具体使用可以看 selenium用法详解

登录问题

使用 Selenium 模拟登录

使用Selenium来进行模拟登录,整个过程非常简单。流程如下。

  1. 初始化ChromeDriver。
  2. 打开知乎登录页面。
  3. 找到用户名的输入框,输入用户名。
  4. 找到密码输入框,输入用户名。
  5. 手动单击验证码。
  6. 按下Enter键。

代码示例如下,以登录知乎为例:

    from selenium import webdriver
    from selenium.webdriver.common.keys import Keys
    import time
    driver = webdriver.Chrome('./chromedriver') #填写你的chromedriver的路径
    driver.get("https://www.zhihu.com/#signin")
    elem = driver.find_element_by_name("account") #寻找账号输入框
    elem.clear()
    elem.send_keys("xxx@gmail.com") #输入账号
    password = driver.find_element_by_name('password') #寻找密码输入框
    password.clear()
    password.send_keys("12345678") #输入密码
    input(’请在网页上点击倒立的文字,完成以后回到这里按任意键继续。')
    elem.send_keys(Keys.RETURN) #模拟键盘回车键
    time.sleep(10)#这里可以直接sleep,也可以使用上一章讲到的等待某个条件出现
    print(driver.page_source)
    driver.quit()

无论是使用Selenium来运行异步加载的网站,还是模拟登录,代码少,效果好,看起来简直完美。但是Selenium的缺点就是它的速度太慢了。如果一个网页有很多图片又有很多的异步加载,那么使用Selenium处理完成一个网页要十几秒甚至几十秒。而且如果是在服务器上使用PhantomJS作为WebDriver,还会出现内存泄漏的问题,爬虫轻轻松松就会把服务器内存撑爆。因此,Selenium不适合用于大规模的爬虫开发。

使用 cookie 登录

在已经登录知乎的情况下,打开Chrome的开发者工具,定位到“Network”选项卡,然后刷新网页,在加载的内容中随便选择一项,然后看右侧的数据,从Request Headers中可以找到Cookie

屏幕截图 2025-01-05 204943.png

代码示例如下:

import requests

# 创建 Session 对象
session = requests.Session()

# 设置请求头,模拟浏览器行为
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'
    'Cookie': '_xsrf=vw5D9Z1Uy5QTOUGKCbpayY1znELcXBce; __snaker__id=m6K9TjhxJeDmlEc2; q_c1=5b9dab0c8cbf4479a4424372f147a2e3|1725801767000|1725801767000; _zap=2af9cfd7-7d59-47cd-93ee-903fbb1c2916; d_c0=AUDS_aTPkxmPTgPCZXRtqCfpwuXUJUtETMo=|1732196165'
}
session.headers.update(headers)

# 发送请求
url = 'https://www.zhihu.com/'
response = session.get(url)
if response.status_code == 200:
    print(response.text)
else:
    print(f"请求失败,状态码: {response.status_code}")

验证码问题

对于验证码问题,有三种解决方案,分别是

  • 使用 Cookie,避免验证码
  • 使用爬虫获取验证码的图片,等待命令行手动输入
  • 使用爬虫获取验证码的图片,使用开源的OCR库pytesseract配合图像识别引擎tesseract,将图片中的文字转换为文本。这种方式在爬虫中的应用并不多见。因为现在大部分的验证码都加上了干扰的纹理,已经很少能用单机版的图片识别方式来识别了。

抓包

mitmproxy 是一个命令行下的抓包工具,它的作用和Charles差不多,但它可以在终端下工作。使用mitmproxy就可以实现自动化的抓包并从数据包里面得到有用的信息。

mitmproxy的强大之处在于它还自带一个mitmdump命令。这个命令可以用来运行符合一定规则的Python脚本,并在Python脚本里面直接操作HTTP和HTTPS的请求,以及返回的数据包。

详情可以看 App爬虫神器mitmproxy和mitmdump的使用

Scrapy

Scrapy是基于Python的分布式爬虫框架。使用它可以非常方便地实现分布式爬虫。Scrapy高度灵活,能够实现功能的自由拓展,让爬虫可以应对各种网站情况。同时,Scrapy封装了爬虫的很多实现细节,所以可以让开发者把更多的精力放在数据的提取上。

干货!一份详实的 Scrapy 爬虫教程,值得收藏!-CSDN博客

法律问题

法律问题有几点需要注意:

  1. 目标网站有反爬虫声明;
  2. 目标网站能在服务器中找到证据(包括但不限于日志、IP);
  3. 目标网站进行起诉。

如果目标网站本身就是提供公众查询服务的网站,那么使用爬虫是合法合规的。但是尽量不要爬取域名包含.gov的网站。

如果能在不登录的情况下爬取数据,那么爬虫就绝不应该登录。这一方面是避免反爬虫机制,另一方面也是减小法律风险。如果必须登录,那么需要查看网站的注册协议和条款,检查是否有禁止将用户自己后台数据公开的相关条文。

robots.txt是一个存放在网站根目录下的ASCII编码的文本文件。爬虫在爬网站之前,需要首先访问并获取这个robots.txt文件的内容,这个文件里面的内容会告诉爬虫哪些数据是可以爬取的,哪些数据是不可以爬取的。要查看一个网站的robots.txt,只需要访问“网站域名/robots.txt”​,例如知乎的robots.txt地址为www.zhihu.com/robots.txt

参考