Python 爬虫实战:从基础抓取到多线程下载优化
本文通过彼岸桌面壁纸网站实例,演示了 Python 爬虫的基础流程。内容涵盖 URL 列表构造、HTML 解析获取图片链接、本地文件保存,并对比了单线程与多线程(生产者 - 消费者模式)的实现差异。重点讲解了 requests、BeautifulSoup 库的使用及 threading 模块中的锁机制,旨在提升爬虫效率。同时补充了 User-Agent 设置、异常处理和频率控制等最佳实践,帮助开发者构建稳定合规的爬虫程序。

本文通过彼岸桌面壁纸网站实例,演示了 Python 爬虫的基础流程。内容涵盖 URL 列表构造、HTML 解析获取图片链接、本地文件保存,并对比了单线程与多线程(生产者 - 消费者模式)的实现差异。重点讲解了 requests、BeautifulSoup 库的使用及 threading 模块中的锁机制,旨在提升爬虫效率。同时补充了 User-Agent 设置、异常处理和频率控制等最佳实践,帮助开发者构建稳定合规的爬虫程序。

我们需要爬取网站上给定页数的图片,首先需要观察各个页面链接之间的关系,进而构造出需要爬取页面的 URL 列表。
http://www.netbian.com/http://www.netbian.com/index_2.htm可以看出,从第二页开始之后的页面链接只是后面的数字不同。我们可以编写代码动态获取页面的 URL 列表。
# 页面链接的初始化列表
page_links_list = ['http://www.netbian.com/']
# 获取爬取的页数和页面链接
pages = int(input('请输入你想爬取的页数:'))
if pages > 1:
for page in range(2, pages + 1):
url = 'http://www.netbian.com/index_' + str(page) + '.htm'
page_links_list.append(url)
else:
pass
print(page_links_list)
运行示例输出:
请输入你想爬取的页数:5
['http://www.netbian.com/', 'http://www.netbian.com/index_2.htm',
'http://www.netbian.com/index_3.htm', 'http://www.netbian.com/index_4.htm',
'http://www.netbian.com/index_5.htm']
我们已经获取了所有页面的链接,但还需要获取每张图片的链接。我们以第一页为例,获取每张图片的链接,其他页面逻辑类似。
在浏览器中右键 -> 查看元素,使用开发者工具定位图片标签。每个页面通常有 18 张图片。通过 CSS 选择器 div.list li a img 可以定位到这些图片标签,img 标签的 src 属性即为图片链接。
import requests
from bs4 import BeautifulSoup
# 页面链接的初始化列表
url = 'http://www.netbian.com/'
# 图片链接列表
img_links_list = []
# 获取 img 标签,并提取图片链接
try:
html = requests.get(url, timeout=5).content.decode('gbk')
soup = BeautifulSoup(html, 'lxml')
imgs = soup.select("div.list li a img")
for img in imgs:
img_link = img['src']
img_links_list.append(img_link)
print(img_links_list)
print(len(img_links_list))
except Exception as e:
print(f"请求失败:{e}")
有了图片链接后,我们需要将图片下载到本地。这里以单张图片下载为例。
import urllib.request
url = 'http://img.netbian.com/file/2019/0817/smalle213d95e54c5b4fb355b710a473292ea1566035585.jpg'
urllib.request.urlretrieve(url, filename='test.jpg')
结合以上三个部分(构造 URL、获取图片链接、下载图片),构造一个完整但效率不高的单线程爬虫。
import requests
from bs4 import BeautifulSoup
import lxml
import urllib.request
import os
import time
# 获取图片并下载到本地
def GetImages(url):
try:
html = requests.get(url, timeout=5).content.decode('gbk')
soup = BeautifulSoup(html, 'lxml')
imgs = soup.select("div.list li a img")
for img in imgs:
link = img['src']
display = link.split('/')[-1]
print('正在下载:', display)
# 确保目录存在
if not os.path.exists('./images'):
os.mkdir('./images')
filename = './images/' + display
urllib.request.urlretrieve(link, filename)
except Exception as e:
print(f"下载失败 {link}: {e}")
# 获取爬取的页数,返回链接数
def GetUrls(page_links_list):
pages = int(input('请输入你想爬取的页数:'))
if pages > 1:
for page in range(2, pages + 1):
url = 'http://www.netbian.com/index_' + str(page) + '.htm'
page_links_list.append(url)
if __name__ == '__main__':
page_links_list = ['http://www.netbian.com/']
GetUrls(page_links_list)
os.makedirs('./images', exist_ok=True)
print("开始下载图片!!!")
start = time.time()
for url in page_links_list:
GetImages(url)
print('图片下载成功!!!')
end = time.time() - start
print('消耗时间为:', end)
以上代码可以完整运行,但由于是顺序下载图片,效率较低。为了解决这个问题,下面我们将使用多线程来实现图片的爬取和下载。
多线程我们使用的是 Python 自带的 threading 模块。这里采用生产者 - 消费者模式:生产者专门用来从每个页面中获取图片的下载链接存储到一个全局列表中;消费者专门从这个全局列表中提取图片链接进行下载。
注意:在多线程中使用全局变量时,必须使用锁(Lock)来保证数据的一致性和线程安全。
import urllib.request
import threading
from bs4 import BeautifulSoup
import requests
import os
import time
import lxml
# 页面链接的初始化列表
page_links_list = ['http://www.netbian.com/']
# 图片链接列表
img_links_list = []
# 获取爬取的页数和页面链接
def GetUrls(page_links_list):
pages = int(input('请输入你想爬取的页数:'))
if pages > 1:
for page in range(2, pages + 1):
url = 'http://www.netbian.com/index_' + str(page) + '.htm'
page_links_list.append(url)
# 初始化锁,创建一把锁
gLock = threading.Lock()
# 生产者,负责从每个页面中获取图片的链接
class Producer(threading.Thread):
def run(self):
while len(page_links_list) > 0:
# 上锁
gLock.acquire()
# 默认取出列表中的最后一个元素
if page_links_list:
page_url = page_links_list.pop()
else:
gLock.release()
continue
# 释放锁
gLock.release()
# 获取 img 标签
try:
html = requests.get(page_url, timeout=5).content.decode('gbk')
soup = BeautifulSoup(html, 'lxml')
imgs = soup.select("div.list li a img")
# 加锁,向全局列表添加图片链接
gLock.acquire()
for img in imgs:
img_link = img['src']
img_links_list.append(img_link)
# 释放锁
gLock.release()
except Exception:
pass
# 消费者,负责从获取的图片链接中下载图片
class Consumer(threading.Thread):
def run(self):
print(f"{threading.current_thread().name} is running")
while True:
# 上锁
gLock.acquire()
if len(img_links_list) == 0:
# 不管什么情况,都要释放锁
gLock.release()
# 检查是否所有任务完成,这里简化处理,实际项目中可用标志位
break
else:
img_url = img_links_list.pop()
# 释放锁
gLock.release()
filename = img_url.split('/')[-1]
print('正在下载:', filename)
path = './images/' + filename
try:
urllib.request.urlretrieve(img_url, filename=path)
except Exception as e:
print(f"下载错误:{e}")
if __name__ == '__main__':
GetUrls(page_links_list)
os.makedirs('./images', exist_ok=True)
start = time.time()
# 5 个生产者线程,去从页面中爬取图片链接
producers = []
for x in range(5):
t = Producer()
t.start()
producers.append(t)
# 10 个消费者线程,去从中提取下载链接,然后下载
consumers = []
for x in range(10):
t = Consumer()
t.start()
consumers.append(t)
# 等待所有线程结束
for p in producers:
p.join()
for c in consumers:
c.join()
end = time.time() - start
print("全部下载完成")
print('消耗时间为:', end)
在实际开发爬虫项目时,除了实现功能,还需注意以下几点以提升稳定性和合规性:
requests.get 中添加 headers。
headers = {'User-Agent': 'Mozilla/5.0'}
response = requests.get(url, headers=headers)
try-except 包裹关键代码块,防止程序崩溃。robots.txt 协议,尊重网站方的爬取规则。time.sleep() 延时。本文通过实例演示了 Python 爬虫从 URL 构造、HTML 解析到文件下载的完整流程。对比了单线程与多线程(生产者 - 消费者模式)的实现差异,重点讲解了 threading 模块中的锁机制以确保数据安全。掌握这些基础技术有助于构建更高效的自动化数据采集工具。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online
解析常见 curl 参数并生成 fetch、axios、PHP curl 或 Python requests 示例代码。 在线工具,curl 转代码在线工具,online
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online