Python 爬虫基础
1. 爬虫基础概念
1.1 什么是网络爬虫?
网络爬虫(Web Crawler),也称为网络蜘蛛(Web Spider)或网络机器人(Web Robot),是一种按照一定规则,自动地抓取万维网信息的程序或脚本。它们模拟浏览器行为,向服务器发送请求,获取网页内容,并从中提取所需数据。
1.2 爬虫的组成部分
一个典型的网络爬虫通常包含以下几个核心部分:
- 请求模块 (Downloader) :负责向目标网站发送 HTTP/HTTPS 请求,获取网页的原始 HTML/JSON/XML 内容。
- 解析模块 (Parser) :负责解析获取到的网页内容,从中提取出目标数据。
- 数据存储模块 (Pipeline/Storage) :负责将提取到的数据保存到文件、数据库或其他存储介质中。
- 调度器 (Scheduler) :管理待抓取的 URL 队列,决定下一个要抓取的页面。
- URL 管理器 (URL Manager) :负责管理已抓取和待抓取的 URL,确保不重复抓取,并处理新的 URL。
2. HTTP/HTTPS 基础
网络爬虫的核心是模拟浏览器发送 HTTP/HTTPS 请求。了解 HTTP/HTTPS 的基本原理对于编写高效、健壮的爬虫至关重要。
2.1 请求方法
- GET: 从服务器获取资源。最常用的方法,用于请求网页、图片等。参数通常附加在 URL 后面(查询字符串)。
- POST: 向服务器提交数据,例如提交表单数据、上传文件。参数通常放在请求体中。
- PUT: 向服务器上传完整的新资源,或替换现有资源。
- DELETE: 请求删除指定资源。
- HEAD: 类似于 GET 请求,但只返回响应头,不返回响应体。常用于检查资源是否存在或获取元数据。
2.2 状态码 (Status Codes)
服务器响应 HTTP 请求时返回的三位数字代码,表示请求的处理结果。
-
2xx (成功) :
200 OK: 请求成功。201 Created: 请求成功,并创建了新资源。204 No Content: 请求成功,但没有返回任何内容。
-
3xx (重定向) :
301 Moved Permanently: 资源永久移动到新位置。302 Found: 资源临时移动到新位置。304 Not Modified: 资源未修改,可以使用缓存。
-
4xx (客户端错误) :
400 Bad Request: 客户端请求语法错误。401 Unauthorized: 未经授权,需要身份验证。403 Forbidden: 服务器拒绝请求,通常是权限问题。404 Not Found: 服务器找不到请求的资源。429 Too Many Requests: 短时间内发送了太多请求(被限流)。
-
5xx (服务器错误) :
500 Internal Server Error: 服务器内部错误。503 Service Unavailable: 服务器暂时无法处理请求(可能过载或维护)。
2.3 请求头 (Request Headers)
请求头包含关于请求或客户端的附加信息。爬虫中常用的请求头:
User-Agent: 标识客户端的类型,模拟浏览器访问可以避免被识别为爬虫。Referer: 表示请求的来源页面。Cookie: 包含会话信息,用于维持登录状态等。Accept-Encoding: 告知服务器客户端支持的编码方式(如 gzip, deflate)。
2.4 响应 (Response)
服务器对请求的回应,包含状态行、响应头和响应体。
- 响应体 (Response Body) : 实际的网页内容(HTML、JSON、图片数据等)。
3. 常用 Python 爬虫库
Python 拥有丰富的第三方库,使得编写爬虫变得非常高效。
3.1 requests:HTTP 请求库
requests 是一个简洁而强大的 HTTP 库,用于发送各种 HTTP 请求。
-
安装:
pip install requests -
发送 GET 请求:
import requests url = "https://www.example.com" try: response = requests.get(url) # 检查响应状态码 response.raise_for_status() # 如果状态码不是200,则抛出HTTPError异常 # 获取响应内容 print(f"URL: {url}") print(f"状态码: {response.status_code}") print(f"响应头: {response.headers}") print(f"编码: {response.encoding}") print(f"响应文本 (前500字符):\n{response.text[:500]}...") print(f"响应字节内容 (前50字节): {response.content[:50]}...") except requests.exceptions.RequestException as e: print(f"请求发生错误: {e}") -
发送带参数的 GET 请求:
import requests url = "https://httpbin.org/get" # 一个用于测试的公共API params = { "name": "Python", "version": "3.9" } response = requests.get(url, params=params) print("\n--- 带参数的 GET 请求 ---") print(response.url) # 打印完整的请求URL print(response.json()) # 打印JSON响应内容 -
发送 POST 请求:
import requests url = "https://httpbin.org/post" data = { "username": "testuser", "password": "testpassword" } response = requests.post(url, data=data) # data 参数用于表单数据 print("\n--- POST 请求 (表单数据) ---") print(response.json()) # 发送 JSON 数据 json_data = { "item": "book", "price": 29.99 } response = requests.post(url, json=json_data) # json 参数用于发送JSON格式数据 print("\n--- POST 请求 (JSON 数据) ---") print(response.json()) -
设置请求头:
import requests url = "https://httpbin.org/headers" headers = { "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36", "Referer": "https://www.google.com" } response = requests.get(url, headers=headers) print("\n--- 设置请求头 ---") print(response.json()) -
超时设置:
import requests url = "https://httpbin.org/delay/5" # 模拟5秒延迟的URL try: response = requests.get(url, timeout=3) # 设置3秒超时 print("\n--- 超时设置 ---") print(response.text) except requests.exceptions.Timeout: print("\n--- 超时设置 ---") print("请求超时!") except requests.exceptions.RequestException as e: print(f"请求发生错误: {e}") -
会话 (Session) :
requests.Session对象允许你在多个请求之间保持某些参数(如 Cookie、请求头)。这对于需要登录或维持会话状态的网站非常有用。import requests session = requests.Session() # 假设登录操作会设置Cookie login_url = "https://httpbin.org/cookies/set/sessioncookie/12345" session.get(login_url) # 再次请求时,Session会自动携带上一次请求设置的Cookie check_cookie_url = "https://httpbin.org/cookies" response = session.get(check_cookie_url) print("\n--- 会话 (Session) ---") print(response.json())
3.2 BeautifulSoup:HTML/XML 解析库
BeautifulSoup 是一个用于从 HTML 或 XML 文件中提取数据的 Python 库。它能够将复杂的 HTML 文档转换成一个复杂的树形结构,每个节点都是 Python 对象。
-
安装:
pip install beautifulsoup4(通常也需要安装一个解析器,如lxml或html5lib)pip install lxml -
基本用法:
from bs4 import BeautifulSoup html_doc = """ <html><head><title>测试页面</title></head> <body> <p class="title"><b>页面标题</b></p> <a href="http://example.com/link1" class="sister" id="link1">链接1</a> <a href="http://example.com/link2" class="sister" id="link2">链接2</a> <p> 这是一个段落。 <span class="highlight">高亮文本</span> 另一个文本。 </p> </body></html> """ soup = BeautifulSoup(html_doc, 'lxml') # 使用 lxml 解析器 print("\n--- BeautifulSoup 基本用法 ---") print(f"页面标题: {soup.title.string}") # 获取 <title> 标签的文本内容 print(f"第一个 p 标签: {soup.p}") # 获取第一个 <p> 标签 print(f"p 标签的 class: {soup.p['class']}") # 获取 p 标签的 class 属性 # 获取所有链接 for link in soup.find_all('a'): print(f"链接文本: {link.get_text()}, URL: {link.get('href')}") -
查找元素:
-
find(name, attrs, recursive, string, **kwargs): 查找第一个匹配的标签。 -
find_all(name, attrs, recursive, string, limit, **kwargs): 查找所有匹配的标签,返回一个列表。name: 标签名 (如'a','p')。attrs: 属性字典 (如{'class': 'title'},{'id': 'link1'})。string: 匹配标签的文本内容。limit: 限制返回结果的数量。
print("\n--- BeautifulSoup 查找元素 ---") # 查找所有 class 为 'sister' 的 <a> 标签 sisters = soup.find_all('a', class_='sister') # class_ 是因为 class 是Python关键字 for s in sisters: print(f"找到 sister 链接: {s.get_text()}") # 查找 id 为 'link1' 的标签 link1 = soup.find(id='link1') print(f"找到 id 为 link1 的标签: {link1.get_text()}") # 查找文本为 "高亮文本" 的 span 标签 highlight_span = soup.find('span', string="高亮文本") if highlight_span: print(f"找到高亮文本: {highlight_span.get_text()}") -
-
CSS 选择器 (推荐) :
select()方法允许你使用 CSS 选择器来查找元素,语法与前端 CSS 类似,非常强大和直观。print("\n--- BeautifulSoup CSS 选择器 ---") # 查找所有 class 为 'sister' 的 <a> 标签 links_by_css = soup.select('a.sister') for link in links_by_css: print(f"CSS选择器找到链接: {link.get_text()}") # 查找 id 为 'link1' 的标签 link1_by_css = soup.select_one('#link1') # select_one 类似 find,只返回第一个 print(f"CSS选择器找到 id 为 link1 的标签: {link1_by_css.get_text()}") # 查找 p 标签下的 span 标签 span_in_p = soup.select('p span.highlight') for s in span_in_p: print(f"CSS选择器找到 p 下的 span: {s.get_text()}")
3.3 lxml:高性能解析库 (常与 XPath 结合)
lxml 是一个高性能的 XML 和 HTML 解析库,它提供了 Pythonic 的 API,并且支持 XPath 和 CSS 选择器。在处理大型或复杂的 HTML/XML 文档时,lxml 通常比 BeautifulSoup 更快。
-
安装:
pip install lxml -
基本用法 (XPath) : XPath 是一种在 XML 文档中查找信息的语言。它提供了一种简洁的方式来定位文档中的节点。
from lxml import etree html_doc = """ <html><head><title>测试页面</title></head> <body> <div id="container"> <p class="intro">这是一个介绍段落。</p> <ul> <li>项目1</li> <li class="item-active">项目2</li> <li>项目3</li> </ul> <a href="/about.html">关于我们</a> <a href="/contact.html">联系我们</a> </div> </body></html> """ # 从字符串解析HTML html_element = etree.HTML(html_doc) print("\n--- lxml XPath 基本用法 ---") # 查找所有 <p> 标签 paragraphs = html_element.xpath('//p') for p in paragraphs: print(f"找到段落: {p.text}") # 查找 class 为 'item-active' 的 <li> 标签 active_item = html_element.xpath('//li[@class="item-active"]') if active_item: print(f"找到活跃项目: {active_item[0].text}") # 查找所有 <a> 标签的 href 属性 links_href = html_element.xpath('//a/@href') print(f"所有链接的 href 属性: {links_href}") # 查找 id 为 'container' 的 div 下的所有 <a> 标签的文本 container_links_text = html_element.xpath('//div[@id="container"]/a/text()') print(f"container 下链接文本: {container_links_text}")
3.4 Scrapy:专业爬虫框架
Scrapy 是一个强大的 Python 爬虫框架,用于快速、高效地抓取网站并从网页中提取结构化数据。它提供了完整的爬虫解决方案,包括请求调度、中间件、管道、分布式部署等。适用于构建复杂和大规模的爬虫项目。
-
安装:
pip install scrapy -
核心组件:
- Spider (爬虫) : 定义如何抓取网站以及如何从网页中提取数据。
- Item (数据项) : 定义要从网页中提取的数据结构。
- Pipeline (管道) : 处理 Item,例如清理、验证、存储数据。
- Downloader Middleware (下载器中间件) : 在请求发送前和响应接收后处理请求和响应(如设置代理、User-Agent)。
- Spider Middleware (爬虫中间件) : 在 Spider 处理输入和输出时进行处理。
-
基本流程:
- 创建一个 Scrapy 项目 (
scrapy startproject myproject)。 - 在项目中创建一个 Spider (
scrapy genspider example example.com)。 - 在 Spider 中定义起始 URL、如何解析响应以及如何提取数据。
- 运行爬虫 (
scrapy crawl example)。
- 创建一个 Scrapy 项目 (
-
示例 (概念性代码,非完整可运行) :
# myproject/myproject/spiders/example_spider.py import scrapy class ExampleSpider(scrapy.Spider): name = 'example' start_urls = ['http://quotes.toscrape.com/'] # 示例网站 def parse(self, response): # 使用 CSS 选择器提取数据 quotes = response.css('div.quote') for quote in quotes: yield { 'text': quote.css('span.text::text').get(), 'author': quote.css('small.author::text').get(), 'tags': quote.css('div.tags a.tag::text').getall(), } # 翻页逻辑 next_page = response.css('li.next a::attr(href)').get() if next_page is not None: yield response.follow(next_page, callback=self.parse) # 在项目根目录运行: scrapy crawl example -o quotes.json
3.5 selenium:自动化浏览器
selenium 主要用于 Web 应用程序的自动化测试,但它也可以用于爬取那些依赖 JavaScript 动态加载内容的网站。它会启动一个真实的浏览器(如 Chrome, Firefox),模拟用户的行为(点击、滚动、填写表单),然后获取渲染后的页面内容。
-
安装:
pip install selenium(还需要下载对应浏览器的 WebDriver,例如 ChromeDriver) -
基本用法:
from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options import time # 配置 ChromeDriver 路径 (根据你的实际路径修改) # service = Service(executable_path='/path/to/chromedriver') # 无头模式 (不显示浏览器界面) chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument("--disable-gpu") # 禁用GPU硬件加速 chrome_options.add_argument("--no-sandbox") # 禁用沙盒模式,用于某些环境 chrome_options.add_argument("--disable-dev-shm-usage") # 解决Docker等环境下的/dev/shm问题 # driver = webdriver.Chrome(service=service, options=chrome_options) # 如果需要指定路径 driver = webdriver.Chrome(options=chrome_options) # 如果chromedriver在PATH中 url = "https://www.baidu.com" # 示例网站 try: driver.get(url) time.sleep(2) # 等待页面加载完成,或使用显式等待 print("\n--- Selenium 自动化浏览器 ---") print(f"当前页面标题: {driver.title}") # 查找搜索框并输入内容 search_box = driver.find_element(By.ID, "kw") search_box.send_keys("Python 爬虫") # 查找搜索按钮并点击 search_button = driver.find_element(By.ID, "su") search_button.click() time.sleep(3) # 等待搜索结果加载 print(f"搜索结果页面标题: {driver.title}") # 获取渲染后的页面HTML # print(driver.page_source[:1000]) # 打印部分HTML except Exception as e: print(f"Selenium 操作发生错误: {e}") finally: driver.quit() # 关闭浏览器
4. 爬虫流程
一个完整的爬虫通常遵循以下步骤:
-
确定目标: 明确要抓取哪个网站,以及需要哪些数据。
-
分析网页:
- 使用浏览器开发者工具(F12)分析网页结构(HTML、CSS、JavaScript)。
- 识别目标数据所在的 HTML 元素(标签、属性、ID、Class)。
- 观察网络请求,了解数据是如何加载的(同步加载、异步 AJAX 加载)。
- 检查
robots.txt文件,了解网站的爬取规则。
-
发送请求: 使用
requests库向目标 URL 发送 HTTP/HTTPS 请求,获取网页内容。- 根据分析结果,选择 GET 或 POST 方法。
- 设置适当的请求头(User-Agent、Cookie、Referer 等)。
- 处理请求参数。
-
解析响应:
- 对于 HTML 页面,使用
BeautifulSoup或lxml将原始 HTML 字符串解析成可操作的对象。 - 对于 JSON 数据,直接使用
response.json()解析成 Python 字典或列表。
- 对于 HTML 页面,使用
-
提取数据:
- 使用解析库提供的选择器(如
BeautifulSoup的find(),find_all(),select()或lxml的 XPath)定位目标数据。 - 提取标签的文本内容、属性值等。
- 使用解析库提供的选择器(如
-
数据清洗与处理:
- 对提取到的数据进行格式化、去重、类型转换等操作。
- 处理缺失值或异常数据。
-
数据存储: 将清洗后的数据保存到本地文件(CSV, JSON, TXT)、数据库(MySQL, MongoDB, SQLite)或其他存储系统。
-
循环与翻页: 如果目标数据分布在多个页面,需要实现翻页逻辑,循环执行上述步骤,直到所有页面都被抓取。
-
异常处理: 捕获并处理网络错误、解析错误、反爬机制触发的错误等。
-
遵守规则: 始终遵守网站的
robots.txt协议,设置合理的抓取频率,避免对网站服务器造成过大压力。
5. 反爬机制与应对策略
网站为了保护自身资源和防止滥用,通常会采取各种反爬机制。了解这些机制并采取相应的应对策略是爬虫工程师的必备技能。
5.1 常见的反爬机制
- 基于 User-Agent 的检测: 服务器检查请求头中的
User-Agent字段,如果不是常见的浏览器 UA,则拒绝访问或返回错误。 - 基于 IP 地址的限制: 短时间内来自同一 IP 地址的请求过多,服务器会暂时或永久封禁该 IP。
- 基于 Cookie/Session 的验证: 网站通过 Cookie 跟踪用户会话,如果请求没有携带正确的 Cookie 或会话过期,则拒绝访问。
- 验证码: 网站在请求频率过高时弹出验证码,要求用户手动输入以证明是人类。
- 动态加载内容 (JavaScript 渲染) : 网站内容通过 JavaScript 异步加载(AJAX),直接请求 HTML 无法获取完整数据。
- 登录/认证: 某些数据需要用户登录后才能访问。
- 数据加密: 传输的数据可能经过加密,需要解密才能获取真实内容。
- 蜜罐 (Honeypot) : 在网页中设置一些隐藏的链接,只有爬虫会访问,一旦访问则被识别并封禁。
- 请求参数加密/签名: 请求中的某些参数可能经过加密或签名,防止直接构造请求。
5.2 应对策略
-
伪装 User-Agent: 在请求头中设置常见的浏览器
User-Agent。可以使用fake_useragent等库随机生成。# headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36"} # requests.get(url, headers=headers) -
使用代理 IP 池: 当一个 IP 被封禁时,切换到另一个 IP。可以购买付费代理服务,或搭建自己的代理池。
# proxies = { # "http": "http://user:password@proxy.example.com:8080", # "https": "https://user:password@proxy.example.com:8080", # } # requests.get(url, proxies=proxies) -
处理 Cookie/Session: 使用
requests.Session维护会话,或手动管理 Cookie。 -
验证码识别:
- 简单验证码: 使用
Pillow(PIL) 进行图像处理,结合pytesseract(Tesseract OCR) 进行识别。 - 复杂验证码: 使用第三方打码平台(付费服务),或深度学习模型进行识别。
- 简单验证码: 使用
-
处理动态加载内容:
- 分析 AJAX 请求: 观察开发者工具中的 XHR/Fetch 请求,直接模拟这些请求获取 JSON 数据。
- 使用
selenium: 启动真实浏览器,等待页面完全渲染后再获取 HTML 内容。 - 使用
Playwright或Puppeteer(Node.js) : 类似的无头浏览器自动化工具。
-
模拟登录: 通过分析登录流程,模拟 POST 请求发送用户名和密码进行登录,获取会话 Cookie。
-
设置请求间隔: 模拟人类行为,在请求之间添加随机的延迟 (
time.sleep()),避免请求频率过高。 -
分布式爬虫: 使用
Scrapy框架或自定义分布式架构,将爬取任务分发到多台机器或多个 IP 上。 -
User-Agent 轮换: 使用一个 User-Agent 列表,每次请求随机选择一个。
-
请求失败重试: 对于临时的网络问题或服务器错误,可以设置重试机制。
6. 道德与法律
在进行网络爬虫时,务必遵守相关的道德规范和法律法规,避免不必要的麻烦。
6.1 道德规范
- 遵守
robots.txt协议: 这是一个网站约定俗成的文件,指示爬虫哪些页面可以抓取,哪些不可以。虽然不是强制性的,但遵守它是对网站的尊重。 - 设置合理的抓取频率: 不要给目标网站服务器造成过大的压力,避免被认为是拒绝服务攻击 (DoS)。
- 尊重网站版权: 抓取到的数据可能受版权保护,未经授权不得用于商业用途或公开发布。
- 保护用户隐私: 不要抓取和泄露用户的个人敏感信息。
- 避免恶意行为: 不进行注入、篡改、破坏网站等行为。
6.2 法律法规
- 数据隐私法: 如 GDPR (欧盟通用数据保护条例)、CCPA (加州消费者隐私法案) 等,规定了个人数据的收集、处理和存储。
- 版权法: 网站内容可能受版权保护,未经授权的复制和分发可能构成侵权。
- 不正当竞争法: 恶意爬取商业数据可能被视为不正当竞争行为。
- 网络安全法: 某些国家和地区有严格的网络安全法律,禁止未经授权的访问和数据获取。
在开始爬取任何网站之前,请务必仔细阅读该网站的使用条款和服务协议。