pyspider进阶:构建自动化图片采集系统(附完整源码)

张开发
2026/4/16 19:05:48 15 分钟阅读

分享文章

pyspider进阶:构建自动化图片采集系统(附完整源码)
1. 从单页爬虫到自动化采集系统第一次用pyspider写爬虫时我只会傻傻地抓取单个页面内容。直到有次需要批量下载某个图库的全部图片手动翻页点到手抽筋才意识到必须让爬虫学会自己翻书。这就是我们今天要解决的痛点——如何把pyspider改造成能自动翻页、递归抓取的智能采集系统。先说说这个系统的核心能力它能像人类浏览网页一样先进入目录页自动点击下一页按钮接着遍历每个详情页最后提取所有图片资源。整个过程完全自动化你只需要泡杯咖啡等着收结果。我去年用这套系统帮设计团队采集了上万张素材图节省了他们90%的收集时间。这里有个关键设计原则递归是爬虫的大脑队列是爬虫的腿。当爬虫发现下一页链接时会把新页面加入待抓取队列递归调用index_page遇到详情页链接时则加入详情处理队列调用detail_page。这种设计让爬虫具备了持续工作的能力。2. 环境配置与避坑指南新手最容易栽在环境配置上。最近帮同事调试时发现Python 3.9环境下直接用pip安装会报语法错误。这是因为pyspider部分依赖库还没完全适配新语法。推荐用Python 3.7虚拟环境python -m venv pyspider_env source pyspider_env/bin/activate # Linux/Mac pip install pyspider pyqueryPhantomJS的安装更是个坑中坑。在Ubuntu 20.04上需要先装这些依赖sudo apt install build-essential chrpath libssl-dev libxft-dev wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-2.1.1-linux-x86_64.tar.bz2 tar xvjf phantomjs-*.tar.bz2 sudo mv phantomjs-2.1.1-linux-x86_64/bin/phantomjs /usr/local/bin启动时如果看到Web界面挤成一团别慌。修改~/.pyspider/config.json{ webui: { port: 5000, cdn: //cdn.jsdelivr.net/npm, debug: true, window_width: 1366, window_height: 768 } }3. 分页抓取的核心逻辑真正考验技术的是分页机制实现。以某图库网站为例其分页结构通常有两种显式分页页面底部有明确页码按钮瀑布流滚动到底部自动加载对于第一种情况用这个CSS选择器准能抓到下一页next_page response.doc(.pagination a:contains(下一页)).attr.href if next_page: self.crawl(next_page, callbackself.index_page)遇到瀑布流网站就得分析XHR请求了。Chrome开发者工具里切换到Network→XHR滚动页面观察新出现的请求。比如某图库的加载API是def index_page(self, response): page response.url.split()[-1] api_url fhttps://example.com/api/load?page{int(page)1} self.crawl(api_url, callbackself.index_page)我曾遇到过分页参数加密的情况这时候需要逆向JS代码。有个取巧的办法直接复制浏览器请求头中的Cookie和Authorization加到crawl_config里crawl_config { headers: { Cookie: 你的登录cookie, X-Requested-With: XMLHttpRequest } }4. 图片链接的智能提取提取图片链接看似简单实则暗藏玄机。常见陷阱包括图片延迟加载data-src代替src响应式图片不同分辨率对应不同srcset背景图CSS background-image这里分享我的万能提取方案def detail_page(self, response): images [] for img in response.doc(img).items(): src img.attr(data-src) or img.attr(src) if src and not src.startswith(data:image): if srcset in img.attr(): src img.attr(srcset).split(,)[0].strip().split( )[0] images.append(src) # 处理CSS背景图 for div in response.doc([style*background-image]).items(): style div.attr(style) match re.search(rurl\((.*?)\), style) if match: images.append(match.group(1)) return {images: images}对于反爬严格的网站可以启用phantomjs渲染self.crawl(url, callbackself.detail_page, fetch_typejs, js_script function() { window.scrollTo(0, document.body.scrollHeight); setTimeout(function(){}, 2000); } )5. 数据存储与接口封装采集到的数据需要结构化存储。我推荐使用SQLiteJSON双备份方案import sqlite3 import json def on_result(self, result): if not result: return # SQLite存储 conn sqlite3.connect(images.db) c conn.cursor() c.execute(CREATE TABLE IF NOT EXISTS images (url TEXT, title TEXT, images TEXT)) c.execute(INSERT INTO images VALUES (?,?,?), (result[url], result[title], json.dumps(result[images]))) conn.commit() # 追加到JSON文件 with open(output.json, a) as f: f.write(json.dumps(result) \n)要提供前端调用的API可以用Flask快速搭建from flask import Flask, jsonify import sqlite3 app Flask(__name__) app.route(/api/images) def get_images(): conn sqlite3.connect(images.db) conn.row_factory sqlite3.Row c conn.cursor() c.execute(SELECT * FROM images) return jsonify([dict(row) for row in c.fetchall()]) if __name__ __main__: app.run()6. 性能优化实战技巧当采集量达到上万规模时这些优化手段能让效率提升10倍并发控制crawl_config { proxy: http://代理IP:端口, timeout: 30, retries: 3, itag: v1, fetch_type: js, process_time_limit: 300, task_limit: 10 # 控制并发数 }去重策略from pyspider.libs.hash import md5string def get_taskid(self, task): return md5string(task[url] str(task[fetch].get(data,)))增量采集config(age24*60*60) # 24小时过期 def index_page(self, response): pass有个容易忽略的性能黑洞是图片下载。建议将下载逻辑分离到独立脚本import requests from concurrent.futures import ThreadPoolExecutor def download_image(url): try: r requests.get(url, streamTrue, timeout10) filename url.split(/)[-1] with open(fimages/{filename}, wb) as f: for chunk in r.iter_content(1024): f.write(chunk) except Exception as e: print(f下载失败 {url}: {str(e)}) with ThreadPoolExecutor(max_workers20) as executor: with open(output.json) as f: for line in f: data json.loads(line) executor.map(download_image, data[images])7. 完整源码解析最后奉上经过实战检验的完整代码关键部分我都加了注释from pyspider.libs.base_handler import * import re class ImageSpider(BaseHandler): crawl_config { headers: { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36, Referer: https://example.com }, timeout: 30 } def __init__(self): self.base_url https://example.com/gallery self.max_page 10 # 控制采集深度 every(minutes24*60) def on_start(self): self.crawl(self.base_url, callbackself.index_page, validate_certFalse) config(age12*60*60) def index_page(self, response): # 提取详情页链接 for item in response.doc(.item a).items(): url item.attr.href if url and url.startswith(self.base_url): self.crawl(url, callbackself.detail_page) # 自动翻页逻辑 if hasattr(self, page_count): self.page_count 1 else: self.page_count 1 if self.page_count self.max_page: next_page response.doc(.next-page).attr.href if next_page: self.crawl(next_page, callbackself.index_page) config(priority2) def detail_page(self, response): title response.doc(h1).text().strip() images [] # 多种方式提取图片 for img in response.doc(img).items(): src self.clean_url(img.attr(data-src) or img.attr(src)) if src: images.append(src) for div in response.doc(.swiper-slide).items(): style div.attr(style) if style and background-image in style: src re.search(rurl\((.*?)\), style).group(1) if src: images.append(self.clean_url(src)) return { url: response.url, title: title, images: list(set(images)) # 去重 } def clean_url(self, url): if not url: return None if url.startswith(//): return https: url if url.startswith(/): return self.base_url url return url.split(?)[0] # 去除URL参数这套系统在实际项目中表现出色但有两个经验值得分享一是记得设置合理的请求间隔避免给目标服务器造成压力二是重要数据一定要有多重备份我有次就因为SQLite文件损坏丢了三天数据。现在我的部署方案是每天自动打包数据库JSON文件上传到云存储。

更多文章