Python网络爬虫实战:爬取新闻网站新闻
简介:本实战案例将指导你如何利用Python编写网络爬虫程序来抓取新闻网站的实时新闻数据。涵盖Python基础语法、HTTP协议、网络请求处理、HTML内容解析、数据提取、存储和异常处理等关键知识点。通过实践操作,你将学习如何使用requests库发送请求、利用BeautifulSoup库解析HTML、应用正则表达式提取数据,并将数据保存至CSV文件。同时,本案例强调遵守网站爬取规则和合法使用数据的重要性。
1. Python基础语法在新闻爬虫中的应用
在新闻爬虫的开发中,Python语言因其简洁的语法和强大的库支持而成为首选。本章我们将探索Python基本语法如何在新闻爬虫项目中得以应用,这包括变量的使用、数据结构的操作以及控制流语句的实现。
1.1 Python变量与数据结构
Python变量是存储信息的容器。在新闻爬虫中,我们通常会使用字符串(string)来存储网页内容、列表(list)来存储多个新闻标题、字典(dictionary)来存储新闻详情与标题的对应关系。例如,我们通过爬虫获取了新闻标题和链接,可以使用以下代码存储它们:
# 存储新闻标题与链接
news_data = []
news_title = "Python爬虫教程"
news_url = "https://example.com/article"
news_data.append({'title': news_title, 'url': news_url})
1.2 控制流语句
控制流语句是程序的逻辑部分,用于确定代码的执行路径。在新闻爬虫中,我们需要根据网页结构来解析新闻信息,这通常涉及到条件判断和循环遍历。例如,若要解析带有特定标签的新闻标题,可以使用以下代码:
# 解析带有特定标签的新闻标题
titles = []
for item in soup.find_all('h2', class_='news_title'):
titles.append(item.text)
这里 soup.find_all 是查找所有符合标签和类名的HTML元素,将它们的文本内容添加到标题列表中。
通过这些基础语法的应用,我们可以构建起一个简单高效的新闻爬虫。后续章节将深入探讨更多与爬虫开发相关的高级话题。
2. HTTP协议与网络请求
2.1 HTTP协议基础
2.1.1 理解HTTP协议的工作原理
HTTP协议,即超文本传输协议(HyperText Transfer Protocol),是一个用于传输超媒体文档(如HTML)的应用层协议。它被设计为通过互联网传输和接收请求/响应数据,是目前Web浏览器和服务器之间通信的基础。
HTTP协议遵循客户端-服务器模型,其中客户端发送一个请求,服务器对请求做出响应。一个基本的HTTP事务包括以下步骤:
- 客户端打开一个TCP连接。
- 客户端发送一个HTTP请求消息到服务器。
- 服务器接收到请求后,进行处理并返回HTTP响应消息。
- 服务器关闭TCP连接,或者如果是持续连接(HTTP/1.1默认),等待下一个请求。
HTTP是无状态协议,这意味着服务器不会保存任何关于客户端的两个请求之间状态的信息。为了解决这一问题,引入了Cookie和Session机制以跟踪会话状态。
2.1.2 请求与响应过程
HTTP请求由三部分组成:
- 请求行:包含请求方法(GET、POST等)、URL以及HTTP版本。
- 请求头部:包含关于客户端环境和请求正文的元数据。
- 请求正文:实际要发送给服务器的数据。
一个典型的GET请求示例如下:
GET /index.html HTTP/1.1
Host: www.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8
Accept-Language: en-US,en;q=0.5
Accept-Encoding: gzip, deflate
Connection: keep-alive
HTTP响应同样由三部分组成:
- 状态行:包含HTTP版本、状态码(如200 OK)和状态码的文本解释。
- 响应头部:包含关于服务器和响应数据的元数据。
- 响应正文:服务器返回的实际数据。
一个典型的响应示例如下:
HTTP/1.1 200 OK
Date: Wed, 21 Oct 2023 07:28:00 GMT
Server: Apache/2.4.1 (Unix)
Content-Type: text/html; charset=UTF-8
Content-Length: 122
Connection: close
<html>
<head><title>An Example Page</title></head>
<body>
<p>Hello, World!</p>
</body>
</html>
了解HTTP请求和响应的结构是实现网络爬虫的基础。
2.2 使用requests库发送网络请求
2.2.1 requests库的安装和配置
requests 是一个Python第三方库,用于简化HTTP请求的发送。它非常易于使用,相比于原生的 urllib 库, requests 更加直观。
安装 requests 库的命令如下:
pip install requests
安装完成后,你可以在Python脚本中通过以下方式导入并使用 requests 库:
import requests
response = requests.get('https://www.example.com')
print(response.text)
上述代码演示了如何使用 requests.get 方法发送一个GET请求,并打印服务器响应的文本内容。
2.2.2 构造GET和POST请求
在使用 requests 库时,构造GET和POST请求都十分简单。
GET请求:
# 发送GET请求
get_response = requests.get('https://www.example.com')
# 使用params传递GET请求参数
params = {'key1': 'value1', 'key2': 'value2'}
get_response = requests.get('https://www.example.com', params=params)
POST请求:
# 发送POST请求
post_data = {'key': 'value'}
post_response = requests.post('https://www.example.com', data=post_data)
# 发送JSON数据的POST请求
import json
post_response = requests.post('https://www.example.com', json=post_data)
requests 库还提供了诸如 HEAD 、 PUT 、 DELETE 、 OPTIONS 等其他HTTP方法的接口。
2.2.3 处理HTTP响应数据
当使用 requests 库发送请求时,服务器返回的响应是一个 Response 对象。这个对象包含了响应的所有信息,例如响应头、响应正文等。
获取响应内容:
# 获取文本响应
text_response = response.text
# 获取二进制响应
binary_response = response.content
# 获取JSON响应
json_response = response.json()
获取响应头部信息:
# 获取单个头部信息
content_type = response.headers['Content-Type']
# 获取所有头部信息
headers = response.headers
检查响应状态码:
# 检查是否请求成功
if response.status_code == 200:
print("Success!")
elif response.status_code == 404:
print("Not Found.")
了解如何处理响应数据是网络爬虫必须掌握的技能之一。
2.3 构建完整的HTTP请求和响应流程
为了将理论转化为实践,我们构建一个简单的HTTP请求和响应流程。假设我们需要爬取某个新闻网站的头条新闻标题,并输出其HTML内容。
import requests
# 目标网站URL
url = "https://www.news-website.com"
# 发送GET请求
response = requests.get(url)
# 检查请求是否成功
if response.status_code == 200:
# 输出HTML内容
print(response.text)
else:
print(f"Error! Status code: {response.status_code}")
以上代码展示了使用 requests 库发送HTTP请求,并检查响应状态码的过程。在实际的网络爬虫开发中,我们通常需要对响应数据进行解析,提取出我们感兴趣的信息,这部分将在后续章节中介绍。
3. HTML内容解析与数据提取
3.1 BeautifulSoup库的引入与应用
3.1.1 BeautifulSoup库的安装和基础使用
Python的 BeautifulSoup 是一个功能强大的HTML和XML的解析库,它使用简单,并且能够快速解析复杂的HTML文档,提取其中的信息。安装 BeautifulSoup 库通常使用 pip ,且多数情况下它与 lxml 解析器一起使用,因为 lxml 解析速度快,容错能力强。安装命令如下:
pip install beautifulsoup4 lxml
一旦安装完成,我们就可以在Python脚本中导入并使用BeautifulSoup了。
from bs4 import BeautifulSoup
# 示例HTML文档
html_doc = """
<html><head><title>The Dormouse's story</title></head>
<body>
<p class="title"><b>The Dormouse's story</b></p>
<p class="story">Once upon a time there were three little sisters; and their names were
<a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
<a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
<a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
and they lived at the bottom of a well.</p>
<p class="story">...</p>
# 使用BeautifulSoup解析HTML文档
soup = BeautifulSoup(html_doc, 'lxml')
# 打印解析后的HTML文档
print(soup.prettify())
3.1.2 解析HTML文档结构
使用BeautifulSoup,我们可以轻松访问和操作HTML文档的各个部分。它提供了一系列方法来选择标签,属性,以及文本内容,包括 find() , find_all() , select() 等。
# 找到所有的<a>标签
a_tags = soup.find_all('a')
for a in a_tags:
print(a.get('href'), a.text)
这段代码会打印出文档中所有 <a> 标签的 href 属性和标签内文本。 BeautifulSoup 对象不仅像字符串一样支持 .text 属性来获取标签的纯文本内容,还像字典一样支持 .get() 方法来获取标签属性。
此外,BeautifulSoup支持CSS选择器,这使得我们可以用类似于CSS的语法来选择HTML文档中的元素。
# 使用CSS选择器来获取具有特定id的标签
link = soup.select_one('#link1')
print(link.text, link.get('href'))
这段代码会找到id为 link1 的元素,并打印其文本和 href 属性。通过使用 BeautifulSoup ,我们可以非常灵活地处理HTML文档,提取我们需要的任何数据。
3.2 正则表达式在数据提取中的作用
3.2.1 正则表达式的定义和语法
正则表达式(Regular Expression)是一串由字符和特殊符号构成的字符串,它描述了一种字符串匹配的模式,用来检索、替换那些符合某个模式(规则)的文本。
在Python中,正则表达式可以使用 re 模块进行处理。下面是一个简单的例子,展示了如何使用 re 模块来匹配特定模式的字符串:
import re
# 待搜索的字符串
text = "今天天气不错,明天也不错。"
# 正则表达式,匹配“天”字后的字符
pattern = r"天(后|上|下)"
# re.search()搜索整个字符串,找到第一个符合模式的子串
match = re.search(pattern, text)
if match:
print("找到匹配的文本:", match.group(0))
上面的代码中, re.search() 函数搜索整个字符串,并返回第一个符合给定模式的匹配对象。如果找到匹配, match.group(0) 将会输出匹配的字符串。
正则表达式语法非常丰富,可以描述非常复杂的匹配规则。例如,使用 ? 代表前面的字符是可选的, * 表示前面的字符可以重复任意次数(包括零次),等等。
3.2.2 使用正则表达式提取特定数据
在网页数据提取中,经常会出现一些不规则的标签或文本格式,这时候利用正则表达式的强大功能可以很方便地提取出我们需要的数据。
# 示例HTML文档
html_doc = """
<p>价格: ¥199.99</p>
<p>折扣价格: ¥179.99</p>
# 使用正则表达式匹配价格信息
pattern = r"¥([\d.]+)"
prices = re.findall(pattern, html_doc)
print("提取的价格信息:", prices)
在这个例子中,我们使用 re.findall() 函数来提取所有的价格信息。这里的正则表达式 ¥([\d.]+) 表示匹配以”¥”开头,后接一个或多个数字或点号的字符串。点号在正则表达式中通常代表任意字符,但在这里我们使用 \d 来表示数字,用 \. 来表示点号,以避免与正则表达式的点号符号冲突。
正则表达式在数据提取中非常有效,尤其当HTML文档结构复杂或不规范时。然而,需要谨慎使用,因为过度复杂的正则表达式可能会使代码难以理解和维护。
3.3 数据提取的综合实践
3.3.1 结合使用BeautifulSoup和正则表达式
在实际的数据提取过程中,我们往往会将BeautifulSoup和正则表达式结合起来使用,以提高数据提取的准确性和灵活性。
from bs4 import BeautifulSoup
import re
# 示例HTML文档
html_doc = """
<ul>
<li>姓名: <a href="/profile/alice">Alice</a></li>
<li>姓名: <a href="/profile/bob">Bob</a></li>
<li>姓名: <a href="/profile/charles">Charles</a></li>
</ul>
# 使用BeautifulSoup解析HTML文档
soup = BeautifulSoup(html_doc, 'lxml')
# 使用正则表达式匹配所有包含"href"属性的<a>标签
for a in soup.find_all('a', href=re.compile('^/profile/')):
# 提取并打印链接文本和链接本身
name = a.text
link = a['href']
print(name, link)
在这个例子中,我们首先使用 BeautifulSoup 的 find_all() 函数来查找所有的 <a> 标签,然后通过正则表达式来进一步筛选出符合特定路径的 href 属性值。通过结合使用这两种工具,我们可以更精确地提取所需数据。
3.3.2 提取信息的实战演示
假设我们要抓取一个论坛上的用户信息,包括用户名和用户链接,下面将展示如何使用BeautifulSoup和正则表达式结合来实现这个目标:
from bs4 import BeautifulSoup
import re
# 假设这是从论坛页面获取的HTML文档
html_doc = """
<div>
<h2>论坛成员</h2>
<ul>
<li>用户名: <a href="/user/12345">John</a></li>
<li>用户名: <a href="/user/67890">Doe</a></li>
</ul>
</div>
# 使用BeautifulSoup解析HTML文档
soup = BeautifulSoup(html_doc, 'lxml')
# 使用正则表达式找到所有用户名的<a>标签
for a in soup.find_all('a', href=re.compile('^/user/')):
# 提取用户名和链接
username = a.text
userlink = a['href']
print(f"用户名: {username} 链接: {userlink}")
通过上述步骤,我们成功提取了论坛成员的用户名和链接。这种结合使用BeautifulSoup和正则表达式的方法在实际的网页数据抓取中非常有效。
在这第三章中,我们从BeautifulSoup库的安装与基本使用,深入到HTML文档的结构解析和信息提取。然后,我们通过正则表达式了解了如何匹配和提取特定的文本和数据模式。在数据提取的综合实践中,我们演示了如何结合使用这两种工具,提供了实战演示,帮助读者更好地理解和掌握数据提取的技巧和方法。在实际应用中,这些技术可以大大简化数据抓取的过程,提高数据提取的准确性和效率。
4. 数据的存储与管理
数据存储与管理是爬虫工作中的重要环节。爬虫获取的大量数据需要被有效地组织和存储,以便后续的数据分析和使用。在本章节中,我们将详细探讨如何使用CSV文件和数据库来存储和管理爬取的数据。
4.1 CSV文件的数据存储
CSV(Comma-Separated Values,逗号分隔值)是一种简单的文件格式,用于存储表格数据,它易于生成和读取。CSV文件通常用于存储和交换文本数据,尤其在数据挖掘和数据清洗工作中非常常见。
4.1.1 CSV格式简介
CSV文件是由纯文本组成的表格数据,其中每行代表一个数据记录,每条记录由一个或多个字段组成,字段之间通常使用逗号、制表符或其他字符分隔。CSV格式的优点在于它是一种开放的、易于读写的格式,几乎所有的编程语言都支持读写CSV文件。但是,由于其简单的文本格式,CSV文件不支持复杂的数据结构,例如多级表头、嵌套表格或富文本内容等。
4.1.2 Python操作CSV文件的代码实现
在Python中,我们可以使用内置的 csv 模块来操作CSV文件。以下是一个简单的例子,展示了如何使用 csv 模块读写CSV文件:
import csv
# 写入CSV文件
with open('output.csv', 'w', newline='', encoding='utf-8') as csvfile:
writer = csv.writer(csvfile)
writer.writerow(['Name', 'Age', 'City'])
writer.writerow(['Alice', '24', 'New York'])
writer.writerow(['Bob', '30', 'Los Angeles'])
# 读取CSV文件
with open('output.csv', 'r', encoding='utf-8') as csvfile:
reader = csv.reader(csvfile)
for row in reader:
print(', '.join(row))
在上述代码中,我们首先使用 csv.writer 对象创建了一个CSV写入器,并使用 writerow 方法写入了数据行。接着,我们使用 csv.reader 对象创建了一个CSV读取器来读取数据,并打印出每一行数据。注意在写入文件时,我们通过设置 newline='' 参数避免在写入时产生额外的空行。
对于大型数据集,处理CSV文件时可能需要考虑性能优化,例如使用 csv.DictWriter 和 csv.DictReader ,它们允许通过列名来读写数据,可以提高代码的可读性和易维护性。
4.2 数据库存储选项的探讨
尽管CSV文件是一种简单方便的存储方式,但在处理大量数据或需要结构化查询时,数据库提供了更为强大的解决方案。
4.2.1 理解数据库存储的优势
数据库存储提供了以下优势:
- 数据结构化存储 :数据库表中的每一列都可以定义数据类型,如整数、浮点数或字符串等,确保数据的一致性和准确性。
- 高效的数据操作 :数据库管理系统(DBMS)提供了一套完整的方法来添加、删除、修改和查询数据。
- 事务管理 :数据库支持事务操作,确保了数据的完整性,使得对数据的修改要么完全成功,要么完全不发生。
- 并发控制 :数据库能有效管理多用户同时访问和修改数据的情况,保证数据的一致性。
4.2.2 Python中数据库操作的基础
Python中有多种方式来操作数据库,包括但不限于SQLite、MySQL、PostgreSQL等。这里我们以SQLite为例,演示如何使用Python内置的 sqlite3 模块来创建和操作数据库。
首先,我们创建一个数据库表:
import sqlite3
# 连接到SQLite数据库
# 数据库文件是test.db,如果文件不存在,会自动在当前目录创建
conn = sqlite3.connect('test.db')
# 创建一个Cursor对象并通过它执行SQL语句
cursor = conn.cursor()
# 创建表
cursor.execute('CREATE TABLE IF NOT EXISTS stock' \
'(date text, trans text, symbol text, qty real, price real)')
# 关闭Cursor
cursor.close()
# 提交事务
conn.commit()
# 关闭数据库连接
conn.close()
接着,我们可以执行插入和查询操作:
import sqlite3
# 连接到SQLite数据库
conn = sqlite3.connect('test.db')
cursor = conn.cursor()
# 插入数据
cursor.execute('INSERT INTO stock VALUES ("2023-01-01","BUY","RHAT",100,35.14)')
# 查询数据
cursor.execute('SELECT * FROM stock WHERE symbol=?', ('RHAT',))
rows = cursor.fetchall()
# 输出查询结果
for row in rows:
print(row)
# 关闭Cursor和Connection
cursor.close()
conn.close()
在上述操作中,我们首先执行了创建表的操作。随后,我们通过执行插入(INSERT)和查询(SELECT)语句来操作数据。使用参数化查询( symbol=? )可以防止SQL注入攻击,并且确保数据的正确处理。
数据库存储通常比CSV文件复杂,但在数据量大且需要复杂查询时,使用数据库是更好的选择。在选择数据库时,我们需要根据数据量、查询复杂性、开发效率和运维成本等多方面因素来决定使用何种数据库系统。
5. 异常处理与程序健壮性
5.1 理解异常处理的重要性
5.1.1 异常与错误的区别
在编程中,错误(Error)和异常(Exception)是两个相关但有所区别的概念。错误通常指程序运行时,由于系统资源限制、外部服务错误、内存溢出等原因导致程序无法继续执行的问题。这类错误往往与程序逻辑本身无关,而是由环境或硬件问题引起,很难通过程序代码完全解决。而异常则更偏向于程序代码中出现的可预见的问题,例如除以零、文件不存在、网络请求失败等。
Python通过异常处理机制提供了处理程序中出现的错误的工具,使程序员能够以一种控制的方式应对这些错误情况,而不是让程序因为错误而崩溃。异常处理机制确保了程序的健壮性,提高了程序对错误的处理能力。
5.1.2 Python中的异常处理机制
Python中的异常处理是通过 try...except 语句块实现的。 try 块中的代码是程序尝试执行的代码,如果执行过程中发生了异常,Python会立即跳转到 except 块中继续执行。 except 块允许程序员定义如何处理特定的异常。
以下是一个简单的异常处理示例:
try:
# 尝试执行的代码
number = int(input("请输入一个整数:"))
inverse = 1 / number
except ValueError:
# 处理输入非整数导致的异常
print("请输入一个有效的整数")
except ZeroDivisionError:
# 处理除以零导致的异常
print("除数不能为0")
except Exception as e:
# 处理其他所有异常
print(f"发生了一个错误:{e}")
else:
# 如果try块中的代码没有引发异常,则执行这里的代码
print("操作成功")
finally:
# 无论是否发生异常,这里的代码都会执行
print("这段代码总是会执行")
在这个示例中,如果用户输入的不是整数或者输入了0,程序将通过异常处理机制给出相应的提示信息。而 else 和 finally 部分分别定义了在没有异常和无论是否发生异常时应该执行的代码块。
5.2 在爬虫中实现异常捕获
5.2.1 常见的爬虫异常场景
在编写网络爬虫时,可能会遇到各种异常情况,例如:
- 网络请求超时(Timeout)
- 网页内容解析错误(如找不到特定的HTML标签)
- 数据库连接失败
- 服务器返回的HTTP状态码不是200
- IP被封禁导致的异常(可能需要代理IP进行请求)
这些异常情况都需要通过异常捕获来处理,以保证爬虫程序的稳定运行和数据采集的完整性。
5.2.2 编写健壮的异常处理代码
在爬虫中编写健壮的异常处理代码,需要明确每种异常发生时应该执行的操作。以下是一个异常处理的爬虫代码段:
import requests
from bs4 import BeautifulSoup
def fetch_page(url):
try:
response = requests.get(url)
response.raise_for_status() # 如果响应状态码不是200,将引发HTTPError异常
except requests.exceptions.HTTPError as http_err:
print(f"HTTP error occurred: {http_err}")
except requests.exceptions.ConnectionError as conn_err:
print(f"Connection error occurred: {conn_err}")
except requests.exceptions.Timeout as timeout_err:
print(f"Timeout error occurred: {timeout_err}")
except requests.exceptions.RequestException as err:
print(f"An error occurred: {err}")
else:
# 正常的页面内容处理逻辑
soup = BeautifulSoup(response.text, 'html.parser')
return soup
try:
url = "https://www.example.com"
page = fetch_page(url)
if page:
# 提取页面中的数据
# ...
pass
except Exception as e:
print(f"程序遇到异常:{e}")
在这个例子中,我们首先使用 try 块来捕获 fetch_page 函数中可能出现的异常。每个 except 子句对应一种不同的异常类型,这样我们可以对不同类型的异常采取不同的处理措施。如果所有异常都未被触发,那么 else 子句中的代码将被执行,即页面正常解析和数据提取。
通过在爬虫代码中加入此类异常处理逻辑,可以有效避免程序因异常而意外终止,并及时记录错误信息,为程序的进一步调试和维护提供了便利。
6. 提升爬虫效率的技术
随着网络数据量的爆炸式增长,传统的单线程爬虫程序已经无法满足高效采集数据的需求。因此,我们需要采用更高级的技术来提升爬虫的效率,以适应大规模数据抓取任务。在本章中,我们将重点探讨如何使用多线程技术和异步IO(包括协程技术)来大幅度提高爬虫的工作效率。
6.1 多线程技术的引入
多线程是一种通过多个线程并行执行任务来提高程序运行效率的方法。在爬虫程序中引入多线程技术,可以同时发送多个网络请求,从而在一定程度上提升数据抓取的效率。
6.1.1 线程的概念和优点
在操作系统中,线程是独立执行路径的最小单元。一个进程可以包含多个线程,这些线程共享进程的资源,并且可以并发执行。引入多线程技术具有以下优点:
- 并发性 :多线程可以同时处理多个任务,提高资源的利用率。
- 响应性 :对于需要用户交互的应用程序,使用多线程可以使得程序更加响应用户操作。
- 资源平衡 :线程可以分配不同的优先级,优化CPU时间的分配,平衡各种任务的处理。
- 简化编程模型 :采用多线程可以将复杂问题简化为多个简单的线程处理。
6.1.2 Python中实现多线程爬虫的方法
Python标准库中的 threading 模块提供了基本的线程操作功能。以下是如何使用Python的 threading 模块实现多线程爬虫的基本步骤:
import threading
import requests
from bs4 import BeautifulSoup
def fetch_url(url):
response = requests.get(url)
if response.status_code == 200:
soup = BeautifulSoup(response.text, 'html.parser')
# 提取数据等操作
# 待爬取的URL列表
urls = ['http://example.com/page1', 'http://example.com/page2', ...]
# 创建线程列表
threads = []
# 创建并启动线程
for url in urls:
thread = threading.Thread(target=fetch_url, args=(url,))
threads.append(thread)
thread.start()
# 等待所有线程完成
for thread in threads:
thread.join()
在上述代码中,我们定义了一个简单的爬取函数 fetch_url ,它接受一个URL作为参数并发送HTTP请求,然后使用 BeautifulSoup 解析响应内容。之后创建了一个URL列表 urls 和线程列表 threads 。通过遍历URL列表,我们为每个URL创建了一个线程,并启动这些线程。使用 thread.join() 确保主线程等待所有子线程完成后才结束。
需要注意的是,在Python中由于全局解释器锁(GIL)的存在,CPU密集型任务不适合使用多线程,但对于I/O密集型任务(如网络请求)来说,多线程能够显著提高性能。此外,过多的线程可能会导致程序管理线程的开销增大,影响整体性能,因此合理控制线程数量是必要的。
6.2 异步IO与协程的使用
异步IO是另一种提升程序执行效率的技术,特别适用于I/O密集型任务。与传统的同步IO不同,异步IO不会让线程阻塞等待I/O操作完成,而是让线程在等待I/O操作完成的空闲时间执行其他任务。
6.2.1 异步IO的基本概念
异步IO(Asynchronous IO)允许程序同时进行I/O操作,而不阻塞当前执行线程。在异步IO模型中,程序发起一个I/O操作后,会立即返回,不会等待该I/O操作完成。I/O操作完成时,会通知程序某个回调函数。
6.2.2 协程的定义和在爬虫中的应用
协程(Coroutines)是实现异步IO的一种方式,它是一种用户态的轻量级线程。协程的调度完全由程序控制,它在一个线程内进行切换,比线程更轻量级,切换开销更小。
在Python中,可以使用 asyncio 库来实现异步IO编程。结合 aiohttp 这个支持异步请求的库,我们可以编写高效且轻量级的异步爬虫。
import asyncio
import aiohttp
async def fetch(session, url):
async with session.get(url) as response:
return await response.text()
async def main():
async with aiohttp.ClientSession() as session:
urls = ['http://example.com/page1', 'http://example.com/page2', ...]
tasks = []
for url in urls:
task = asyncio.ensure_future(fetch(session, url))
tasks.append(task)
responses = await asyncio.gather(*tasks)
# 使用返回的数据
# 运行异步主函数
asyncio.run(main())
在上述代码中, fetch 函数使用 aiohttp 库发送异步请求, main 函数中创建了一个 ClientSession 会话,并为每个URL创建一个异步任务。 asyncio.gather 函数用于并行执行所有的异步任务,等待所有任务完成后返回响应数据。
协程可以大幅提升爬虫效率,尤其是在处理高并发和大数据量的爬取任务时。使用 asyncio 和 aiohttp 可以实现高性能的异步爬虫,而且相比多线程,协程通常有更低的内存占用和更高的执行效率。
本章节详细介绍了多线程和异步IO两种提升爬虫效率的技术,并展示了实际的代码实现。合理运用这些技术,可以显著提高爬虫程序的性能,满足更加复杂和高效的网络数据采集需求。
7. Scrapy框架(选学)的应用实践
Scrapy是一个快速、高层次的网页抓取和网页爬取框架,用于抓取网站并从页面中提取结构化的数据。本章我们将深入探讨Scrapy框架的高级应用,包括如何构建Scrapy爬虫项目,Scrapy管道的数据处理与存储,以及Scrapy的性能优化与维护技巧。
7.1 Scrapy框架简介
7.1.1 Scrapy框架的特点
Scrapy框架由以下几个显著的特点:
- 快速: Scrapy是用纯Python实现的,它拥有出色的性能,并且它内置了异步网络框架Twisted,能够处理高并发的网页抓取请求。
- 高效: 提供了多样化的选择器和数据提取方式,使得数据提取变得异常容易。
- 可扩展性: 拥有丰富的中间件、管道和扩展机制,易于扩展和定制。
- 健壮性: Scrapy自带的错误处理机制能够优雅地处理各种异常和错误。
7.1.2 Scrapy框架的主要组件
Scrapy框架的主要组件包括:
- Engine: 负责控制数据流在系统中的所有组件之间流动,并在相应动作发生时触发事件。
- Spider: 网站爬取的代码编写处,负责解析响应数据并提取数据,同时发起新的请求。
- Item: 指的是从网站爬取到的数据项目模型,由Scrapy爬虫定义的字段结构。
- Item Pipeline: 数据清洗和验证的组件,负责处理从spider传递过来的Item。
- Downloader: 负责下载网页内容,并将网页内容传递给spider。
- Downloader Middlewares: 下载器中间件,介于引擎和下载器之间,可以处理下载器的响应。
- Spider Middlewares: 爬虫中间件,介于引擎和爬虫之间,可以处理爬虫的请求和响应。
7.2 Scrapy框架的高级应用
7.2.1 构建Scrapy爬虫项目
构建一个新的Scrapy项目通常使用Scrapy提供的命令行工具,以下是步骤:
- 打开终端或命令提示符,输入以下命令开始创建一个Scrapy项目:
bash scrapy startproject myspider - 进入创建的项目目录,生成一个爬虫:
bash cd myspider scrapy genspider example example.com - 编辑生成的爬虫文件,通常位于
myspider/spiders目录下,定义要爬取的网页、提取数据的字段和解析逻辑。
7.2.2 Scrapy管道的数据处理与存储
Scrapy管道(Pipeline)是处理爬取到的数据的组件。以下是一个简单的管道代码示例,用于将提取的数据存储到CSV文件:
import csv
class MySpiderPipeline(object):
def open_spider(self, spider):
self.file = open('items.csv', 'w+b')
self.csv_writer = csv.writer(self.file)
self.csv_writer.writerow(['Name', 'Age']) # 表头
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
self.csv_writer.writerow([item['name'], item['age']])
return item
要启用这个管道,需要在项目的 settings.py 文件中添加以下代码:
ITEM_PIPELINES = {
'myspider.pipelines.MySpiderPipeline': 300,
}
7.2.3 Scrapy的性能优化与维护技巧
Scrapy框架提供了许多性能优化的选项,例如:
- 使用
AUTOTHROTTLE扩展控制爬虫的下载速率,避免过快地发送请求。 - 开启
DNS缓存和HTTP缓存以减少延迟和网络请求。 - 使用
代理池和IP轮换策略来应对目标网站的反爬虫机制。 - 定期清理爬虫项目中的
settings.py文件,删除不再使用的配置项,以保持清晰和高效。
这些优化策略能够提升爬虫的效率,并且延长爬虫的有效运行时间。
简介:本实战案例将指导你如何利用Python编写网络爬虫程序来抓取新闻网站的实时新闻数据。涵盖Python基础语法、HTTP协议、网络请求处理、HTML内容解析、数据提取、存储和异常处理等关键知识点。通过实践操作,你将学习如何使用requests库发送请求、利用BeautifulSoup库解析HTML、应用正则表达式提取数据,并将数据保存至CSV文件。同时,本案例强调遵守网站爬取规则和合法使用数据的重要性。
更多推荐
所有评论(0)