尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Scrapy爬虫实战:新片场视频列表数据抓取与MySQL存储
1. 为什么拿新片场练手页面结构分析与数据价值判断接触爬虫的人应该都有这种感觉学完 Scrapy 基础语法之后最大的困惑不是框架不会用而是不知道拿什么练手。淘宝、京东反爬太重豆瓣、知乎又太常见拿来做项目显得没啥技术含金量。年初我正好有视频素材分类整理的需求就把目光放到了新片场这个平台。新片场是专业视频创作人社区里面聚集了大量原创短片、广告、宣传片、纪录片作品。和抖音、B站这类平台比它有几个非常适合爬虫入门的特性第一列表页是传统的服务端渲染页面HTML 里直接带作品卡片数据不需要额外解析 JS 加载的动态接口。第二URL 规律极其明显/videos?page1、/videos?page2这样按页码递增即可翻页没有复杂的签名参数。第三视频封面图和作者头像都在 CDN 上响应头对 UA 的校验不算严格只要伪装成正常浏览器基本不会被拦。第四也是我认为最重要的一点——这里面的每条视频数据质量非常高。作品标题、封面图、播放量、点赞数、作者信息、视频时长、上传时间都是结构化的直接可以作为内容分析、创作者研究、视频行业观察的数据源。我当时的诉求是想按城市宣传片这个主题积累一批案例素材后来干脆扩展成完整爬取前 20 页所有视频数据。20 页每页 24 条左右总共也就 500 条上下。这个量级对本地 MySQL 完全没压力对新手来说也能在几分钟内跑完不会因为数据量太大导致 Debug 难度飙升。等这 500 条数据跑通了后面想扩展到 100 页、1000 页逻辑一模一样只是调整翻页上限而已。有人可能会问为什么不直接爬详情页详情页有播放页地址、视频文件直链、更详细的描述信息数据看起来更丰富。但我的建议是新手阶段先爬列表页原因很简单——列表页数据结构统一字段都在同一个卡片节点下Selector 写起来不容易出错。详情页每个作品版式可能不同有的挂了付费链接有的有剧照有的嵌入外部播放器异常处理成本高。把列表页跑顺了再去研究详情页就属于增量学习了。这一篇我只讲列表页数据入库的完整链路包括页面解析、字段设计、Pipeline 连接 MySQL、全流程 Debug。整套代码跑下来大概 400 行左右但每一步背后的逻辑值得细细说清楚。2. 前置准备环境、依赖库和目录规划2.1 环境版本说明写这篇文章时我本地用的环境是组件版本Python3.11.xScrapy2.11.xMySQL8.0.xpymysql1.1.xscrapyd可选1.2.xScrapy 从 2.x 版本开始对 Python 3.11 支持得很完整直接用 pip 安装不会遇到依赖冲突。新版 Scrapy 自带的scrapy startproject命令生成的项目结构也比较清爽没有多余的模板代码。需要注意的是如果你是 Python 3.12 或者 3.13部分三方的 Twisted 依赖可能需要等待适配我建议新手先用 3.10 或 3.11省去不必要的环境问题。这一步看起来不起眼实际能帮你省下至少半小时的排查时间。2.2 创建项目创建项目的命令很简单scrapy startproject xinpianchang_spider cd xinpianchang_spider创建完成后目录结构长这样xinpianchang_spider/ ├── scrapy.cfg └── xinpianchang_spider/ ├── __init__.py ├── items.py ├── middlewares.py ├── pipelines.py ├── settings.py └── spiders/ └── __init__.py我要操作的文件一共四个items.py定义抓取字段、pipelines.py写 MySQL 逻辑、settings.py配置管道和请求头、以及自己新建的spiders/videos.py写解析逻辑。这个划分是 Scrapy 的约定俗成刚开始可能会觉得文件有点多但项目一旦复杂起来你会感谢这种结构化的约束。2.3 MySQL 创建数据库进 MySQL 命令行或者用可视化工具执行CREATE DATABASE IF NOT EXISTS scrapy_data DEFAULT CHARSET utf8mb4; USE scrapy_data; CREATE TABLE IF NOT EXISTS xinpianchang_videos ( id INT AUTO_INCREMENT PRIMARY KEY, title VARCHAR(255) NOT NULL, video_url VARCHAR(500) DEFAULT NULL, cover_url VARCHAR(500) DEFAULT NULL, author_name VARCHAR(100) DEFAULT NULL, author_url VARCHAR(500) DEFAULT NULL, play_count INT DEFAULT 0, like_count INT DEFAULT 0, duration VARCHAR(20) DEFAULT NULL, created_date VARCHAR(20) DEFAULT NULL, page_no INT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, UNIQUE KEY uk_video_url (video_url) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;字段类型设计上我把播放量和点赞数定成INT标题用VARCHAR(255)封面和链接用VARCHAR(500)。日期字段我这里直接用VARCHAR(20)保存原始字符串因为新片场列表页显示的是2023-05-12这种格式没有时分秒没必要转成 DATETIME后续真要分析时间分布字符串也能拆Year、month、day。这里有一个关键的工程决策给video_url加了唯一索引。这个字段在新片场站点上是唯一的作品 ID 对应一个 URL用它做去重非常稳妥。后续重复运行爬虫数据不会成倍堆积这是做增量爬取的关键一步。3. Items 定义与字段命名先想清楚要什么再动手写代码3.1 字段设计的原则很多人写 Scrapy 爬虫最容易犯的错误是打开浏览器 DevTools 看到什么就抓什么字段名随手起到最后清洗数据的时候一团浆糊。我建议你先想清楚下游要拿这些数据做什么再回头定字段。以新片场为例我当时的分析需求是按作者维度统计作品数量、平均播放量按视频时长分布观察作品类型偏好分析标题文本特征提取城市名类型词按日期观察更新频率所以最小字段集合就是标题、作品链接、封面、作者、作者主页、播放数、点赞数、时长、上传日期、页码。封面 URL 虽然分析时不太用得到但它可以用于后续的图片下载 pipeline避免二次请求页面所以值得存下来。字段太多也有问题——每个字段都是解析成本和异常处理的负担。像视频标签列表作品简介这种列表页没有直接呈现的先不碰。3.2 Items 代码来看items.py的完整代码import scrapy class XinpianchangVideoItem(scrapy.Item): 新片场视频列表页数据模型 title scrapy.Field() # 作品标题 video_url scrapy.Field() # 作品详情页链接 cover_url scrapy.Field() # 封面图地址 author_name scrapy.Field() # 作者名称 author_url scrapy.Field() # 作者个人主页链接 play_count scrapy.Field() # 播放量 like_count scrapy.Field() # 点赞数 duration scrapy.Field() # 视频时长如 02:35 created_date scrapy.Field() # 发布/更新日期 page_no scrapy.Field() # 来源页码你可能会问为什么不像 Django ORM 那样写字段类型Scrapy Item 的Field()本质上就是个字典容器只是附加了 item 的接口语义。它的灵活之处在于 Pipeline 处理阶段可以直接按字段名取值也可以做类型转换。这也是爬虫框架和 Web 框架的思路差异——爬虫里的脏数据太多字段类型本身不是最重要的清洗逻辑才是重点。有些新手喜欢用字典直接传省掉 Items 这个文件。但我的建议是别省。字典的字段名写错了错误会在 Pipeline 里面悄然蔓延你从 MySQL 表里查数据的时候根本不知道是丢了字段还是没抓回来。Items 保证每个 item 有统一结构配合item.get()取值排错时一眼就能看出问题。4. Spider 编写Request 调度、翻页逻辑与列表页解析4.1 start_urls 与翻页规划spiders/videos.py的核心逻辑分三块发起初始请求、解析列表页、生成下一页请求。先写框架import scrapy from xinpianchang_spider.items import XinpianchangVideoItem class VideosSpider(scrapy.Spider): name videos allowed_domains [xinpianchang.com] start_urls [https://www.xinpianchang.com/videos?page1] total_pages 20 # 爬取前20页这里的total_pages 20不是 Scrapy 框架给的规范而是你自己定义的控制变量。实际项目中翻页上限可以由命令行参数动态传入后续我会讲怎么改造成-a参数接收这里先用常量保持简单。4.2 列表页解析核心 Selector 的编写过程新片场列表页的作品卡片每个视频都包含在一个li节点下。我第一步打开页面在 DevTools 里选中一个卡片观察它的 class 特征后先写一个粗粒度的选择器验证def parse(self, response): video_list response.xpath(//ul[classvideo-list]/li) self.logger.info(f本页共抓取到 {len(video_list)} 条视频数据)不要一上来就写完整字段先确认容器节点对不对这是爬虫调试的基础习惯。如果这里就返回 0说明页面结构变了得重新分析如果返回 24再往下精细化。确认了容器能取到数据之后我根据实际 DOM 结构逐步定位每个字段。标题选择器.//a[classtitle]/text()作品详情页链接.//a[classtitle]/href这里要注意一个细节列表页的href可能是相对路径比如/a/12345而我们要存入数据库的是完整 URL。所以需要做拼接处理video_url response.urljoin(item_url)response.urljoin()是 Scrapy 提供的非常常用的工具方法它会把相对路径基于当前响应 URL 拼接成绝对地址。如果原始 URL 已经是完整的https://...开头urljoin会直接返回原值不会做画蛇添足的拼接。封面图、作者信息、播放量、点赞数的定位逻辑也类似。这里有一个容易踩坑的点播放量在新片场的列表页里面是简化显示比如1.2w表示 120003400表示 3400。像这种带单位的数据不能直接存INT必须先做转换。4.3 播放量1.2w格式的转换处理我在解析里写了一个独立的转换函数def parse_count(self, value): 将字符串形式的播放量/点赞数转换为整数 例如: 1.2w - 12000, 3400 - 3400, -- - 0 if not value: return 0 value value.strip() if value.endswith(w): num_part value[:-1] try: return int(float(num_part) * 10000) except ValueError: return 0 try: return int(value) except ValueError: return 0这个函数逻辑不复杂但值得说一说。新片场列表页对播放量的显示规则是1 万以内显示原始数字超过 1 万显示x.xw。用cost或者replace(w, )这种字符串替换方式再强转int也不是不行但不够健壮——万一后面页面抽风出现10w或者1.3k呢先endswith(w)判断再浮点数转换最后返回整数三种情况都兼容了。至于点赞数新片场页面格式类似。有的列表项里点赞数显示为赞 356我就也做了一个replace(赞, ).strip()的小处理再传给parse_count。4.4 完整 parse 方法实现下面给出一版直接可用的列表页解析代码def parse(self, response): video_list response.xpath(//ul[contains(class, video-list)]/li) self.logger.info(f页面 {response.url} 共解析到 {len(video_list)} 条数据) for li in video_list: item XinpianchangVideoItem() raw_title li.xpath(.//a[contains(class, title)]/text()).get() item[title] raw_title.strip() if raw_title else raw_url li.xpath(.//a[contains(class, title)]/href).get() if raw_url: item[video_url] response.urljoin(raw_url) else: # 缺少链接的数据无法去重直接跳过 continue cover_url li.xpath(.//img/data-src | .//img/src).get() item[cover_url] cover_url if cover_url else item[author_name] li.xpath( .//div[contains(class, author)]//a/text() ).get(default).strip() author_url li.xpath( .//div[contains(class, author)]//a/href ).get() item[author_url] response.urljoin(author_url) if author_url else play_text li.xpath( .//span[contains(class, play)]/text() ).get(default) like_text li.xpath( .//span[contains(class, like)]/text() ).get(default) item[play_count] self.parse_count(play_text) item[like_count] self.parse_count(like_text) duration_text li.xpath( .//span[contains(class, time)]/text() ).get(default) item[duration] duration_text.strip() if duration_text else date_text li.xpath( .//span[contains(class, date)]/text() | .//p[contains(class, date)]/text() ).get(default) item[created_date] date_text.strip() if date_text else item[page_no] self.current_page yield item # 翻页逻辑 if self.current_page self.total_pages: next_page self.current_page 1 next_url fhttps://www.xinpianchang.com/videos?page{next_page} yield scrapy.Request(urlnext_url, callbackself.parse)这段代码在解析逻辑上做了一些容错。比如get(default)保证拿不到节点时不会抛异常contains(class, title)比classtitle更不容易被页面 class 增加导致的变更击穿img的>def __init__(self, total_pages20, *args, **kwargs): super().__init__(*args, **kwargs) self.total_pages int(total_pages) self.current_page 1这样启动命令可以写成scrapy crawl videos -a total_pages20也可以不传参数用默认 20。-a keyvalue参数会以字符串形式传入 Spider 的__init__所以必须用int()强转一次。这里有个非常隐蔽的坑如果你在__init__里忘了super().__init__(spider.name和logger会初始化失败直接报AttributeError。新手往往会在这里卡很久。4.6 start_requests 的另一种写法如果你的翻页 URL 规则满足等差数列也可以用start_requests()一次性生成前 20 个请求def start_requests(self): for page in range(1, self.total_pages 1): url fhttps://www.xinpianchang.com/videos?page{page} yield scrapy.Request(urlurl, callbackself.parse, meta{page_no: page})然后用response.meta[page_no]拿到当前页码就不用在 Spider 内部维护current_page了。这种写法的好处是并行度拉满——Scrapy 会同时调度 20 个请求速度更快。坏处是如果服务器返回 429 或者被限制了一上来就是 20 个请求同时打容易踩反爬。我实际测试下来新片场对小并发爬虫容忍度不错但保守起见还是用了顺序翻页的写法控制爬取节奏后面讲反爬设置时再展开。4.7 常见的 XPath 提取问题与调试技巧我写蜘蛛时一般不会盲写选择器每写一个字段就在 Scrapy Shell 里验证一次。这是我自己最推荐的调试方式scrapy shell https://www.xinpianchang.com/videos?page1然后在 shell 环境里一步步测试response.xpath(//ul[contains(class, video-list)]) response.xpath(//ul[contains(class, video-list)]/li) response.xpath(//ul[contains(class, video-list)]/li[1]//a[contains(class, title)]/text()).get()Shell 的一个好处是如果某条 XPath 取不到数据你可以用response.xpath(//*[contains(class, title)])先随便取一批节点反向观察 DOM 的真实结构然后再写更精确的选择器。有些开发者喜欢用浏览器插件直接 Copy XPath但复制出来的 XPath 往往很长、很脆弱页面一改版就废了。contains(class, ...) 相对路径./的组合是长期维护成本最低的一种方式。调试中还有一个非常实用的视图工具from scrapy.shell import inspect_response inspect_response(response, self)把它放在parse方法里运行爬虫时只要检测到该 URL 就会进入调试交互模式可以边跑边试不同的 XPath。用完后记得删掉否则爬虫会一直卡住。5. Settings 与中间件请求头伪装、下载延迟与并发控制5.1 必须改的三个配置项创建完项目之后settings.py里大部分默认值可以直接用但下面三个配置必须关注# robots.txt 协议 ROBOTSTXT_OBEY False # 默认请求头 DEFAULT_REQUEST_HEADERS { User-Agent: ( Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 ), Accept: text/html,application/xhtmlxml,application/xml;q0.9,*/*;q0.8, Accept-Language: zh-CN,zh;q0.9,en;q0.8, Referer: https://www.xinpianchang.com/, } # 下载延迟 DOWNLOAD_DELAY 1.0 # 并发请求数 CONCURRENT_REQUESTS 8ROBOTSTXT_OBEY之所以设成False不是说要无视目标站点规则而是 Scrapy 的 robots 中间件对部分站点的 robots.txt 解析比较死板常常因为一个Allow规则写得奇怪就误拦截正常请求。你在真实项目中还是应该先看一眼目标网站的 robots.txt 再决定。新片场的 robots.txt 对普通爬虫没有特殊限制我这里设False纯粹是为了减少不必要的下载器中间件开销。下载延迟设为 1 秒20 页数据总共也就 20 秒左右不伤大雅。没有人希望自己维护的网站被爬虫打崩作为从业者自控节奏是最基本的职业道德。这个延迟在后面跑全流程时基本不会让人等得不耐烦。并发数CONCURRENT_REQUESTS我设成 8顺序翻页的情况下实际并发也不会超过这个数。如果你用的是顺序翻页写法那真正同时进行的就是 1 个请求这个值更多是给多 tab 场景留裕量。5.2 User-Agent 中间件有些页面会根据 UA 返回不同内容比如某些站点对非浏览器 UA 直接返回 403 或者空页面。为了保险我在middlewares.py里写了一个简单的 UA 轮换中间件import random USER_AGENT_LIST [ Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36, Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36, Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36, ] class RandomUserAgentMiddleware: def process_request(self, request, spider): request.headers[User-Agent] random.choice(USER_AGENT_LIST)然后别忘了在settings.py里启用DOWNLOADER_MIDDLEWARES { xinpianchang_spider.middlewares.RandomUserAgentMiddleware: 543, }数字 543 是优先级没有严格的规范要求只要比 Scrapy 内置的UserAgentMiddleware默认 400 左右大、比RetryMiddleware默认 550 左右小即可。这样既能在请求发出前覆盖默认 UA又不会影响重试机制。不过说实话新片场目前的校验策略对 UA 并不敏感不加这个中间件也能跑通。我这里写它更多是想演示一种面向未知站点的防御性写法。真正遇到反爬严格的平台UA 轮换只是最基础的一层后续还可能需要 Cookie 池、代理 IP 池那就属于进阶话题了。5.3 为什么建议添加 Referer有些平台的图片 CDN 会校验 Referer虽然视频列表页的封面图不一定触发但加上 Referer 可以大幅降低出问题的概率。我把Referer默认设成新片场首页这样页面内嵌套的资源请求看起来更像是从站内发起的。DEFAULT_REQUEST_HEADERS { ... Referer: https://www.xinpianchang.com/, }6. Pipeline 连接 MySQLpymysql 的使用与去重逻辑6.1 为什么选 pymysqlScrapy 官方文档里推荐过scrapy-mysql-pipeline之类的第三方库但我的态度是项目太小的时候用第三方库反而引入不确定性。直接写 pymysql 最透明、最好控制。连接、插入、去重、异常处理全在几十行代码内出了问题能一眼定位。pymysql 是纯 Python 实现的 MySQL 客户端驱动安装命令pip install pymysql它的 API 基本对标 MySQLdbconnect()、cursor()、execute()、commit()、close()这些操作对于写过 Python 数据库代码的人都不陌生。6.2 Pipeline 完整代码下面是pipelines.py的完整实现import pymysql from itemadapter import ItemAdapter class MysqlPipeline: 将抓取到的视频数据写入 MySQL def open_spider(self, spider): self.connection pymysql.connect( host127.0.0.1, port3306, userroot, passwordyour_password, databasescrapy_data, charsetutf8mb4, cursorclasspymysql.cursors.DictCursor, ) self.cursor self.connection.cursor() spider.logger.info(MySQL 数据库连接成功) def process_item(self, item, spider): adapter ItemAdapter(item) sql INSERT INTO xinpianchang_videos (title, video_url, cover_url, author_name, author_url, play_count, like_count, duration, created_date, page_no) VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE title VALUES(title), play_count VALUES(play_count), like_count VALUES(like_count) values ( adapter.get(title), adapter.get(video_url), adapter.get(cover_url), adapter.get(author_name), adapter.get(author_url), adapter.get(play_count), adapter.get(like_count), adapter.get(duration), adapter.get(created_date), adapter.get(page_no), ) try: self.cursor.execute(sql, values) self.connection.commit() spider.logger.info(f数据已写入: {adapter.get(title)}) except Exception as e: self.connection.rollback() spider.logger.error(f写入失败: {e}) return item def close_spider(self, spider): self.cursor.close() self.connection.close() spider.logger.info(MySQL 数据库连接已关闭)这段代码里有几个关键点要展开说6.3 ON DUPLICATE KEY UPDATE 的妙用前面建表时给video_url加了唯一索引这里就用上了。语法INSERT INTO ... ON DUPLICATE KEY UPDATE当插入的数据和表中已有数据发生唯一键冲突时INSERT就不会报错而是执行UPDATE更新指定字段。我这里更新了title、play_count、like_count这样重复运行爬虫时播放量和点赞信息能刷新而不会产生重复行。如果不用这个语法每次重跑爬虫都要先SELECT查重再INSERT多一次数据库往返不说还容易遇到并发写入时的重复风险。一个唯一索引 一条ON DUPLICATE KEY UPDATE在爬虫这个场景里是最优雅的幂等写入方式。6.4 为什么用 ItemAdapter 而不是直接操作 Item旧版 Scrapy 里process_item收到的是 Item 对象直接item[title]取值没问题。但新版 Scrapy 引入了itemadapter统一了 Item、字典、Dataclass 三种数据结构。用ItemAdapter(item).get(title)可以在所有数据形态间无缝切换。实际上我这里因为用的是标准 Scrapy Item直接用item[title]也没毛病。但养成用ItemAdapter的习惯后续如果改成字典存储或者 Pydantic 模型Pipeline 不需要动。6.5 从 settings 读取数据库参数为了避免把账号密码硬编码在 Pipeline 里我建议把数据库配置放进settings.py# MySQL 配置 MYSQL_HOST 127.0.0.1 MYSQL_PORT 3306 MYSQL_USER root MYSQL_PASSWORD your_password MYSQL_DB scrapy_data对应地修改 Pipelinedef open_spider(self, spider): self.connection pymysql.connect( hostspider.settings.get(MYSQL_HOST, 127.0.0.1), portspider.settings.get(MYSQL_PORT, 3306), userspider.settings.get(MYSQL_USER, root), passwordspider.settings.get(MYSQL_PASSWORD, ), databasespider.settings.get(MYSQL_DB, scrapy_data), charsetutf8mb4, )这样数据库变动时只需要改settings.py不用进代码改逻辑。后面如果部署到服务器或用环境变量动态覆盖也更方便。6.6 启用 Pipeline写完 Pipeline 后还必须让 Scrapy 知道该启用它ITEM_PIPELINES { xinpianchang_spider.pipelines.MysqlPipeline: 300, }数字 300 是 Pipeline 的执行优先级数字越小越先执行。如果后面要加图片下载 Pipeline 或者其他清洗 Pipeline可以通过调整数字来控制执行顺序。这里只有一个 Pipeline300 就是随便选的一个标准值。7. 全流程运行、Debug 与数据校验7.1 运行命令与日志观察所有代码写完之后启动爬虫scrapy crawl videos -a total_pages20控制台会输出大量日志。我最关心的几行本页共抓取到 xx 条视频数据— 确认每页都解析到了非零数量的视频数据已写入: 某作品标题— 确认一条条数据被成功插入MySQL 数据库连接已关闭— 确认爬虫结束后管道正确关闭如果日志里出现异常不要慌按下面顺序排查。7.2 典型问题一页面解析为空症状日志显示共抓取到 0 条视频数据但是浏览器打开页面明明有内容。原因大概率是 Scrapy 默认 UA 被识别或者页面结构变更。先做两件事用scrapy shell检查当前响应内容长度看是不是被反爬拦截len(response.body) response.xpath(//title/text()).get()如果返回的 HTML 是空壳或者验证页面说明 UA 需要调整改settings.py里的DEFAULT_REQUEST_HEADERS或者添加 Cookie。新片场目前的验证策略不算严在本地用浏览器 UA 测试基本不会触发。但如果你用的是数据中心 IP、无头浏览器部署可能碰到需要加 Cookie 的情况。大多数爬虫新手失败在这一步主要就是没有区分页面结构变和被反爬拦。7.3 典型问题二MySQL 写入报错如果 Pipeline 报错最常见的两类第一类是密码或库名写错pymysql.err.OperationalError: (1045, Access denied for user rootlocalhost)直接去settings.py里面核对数据库账号和密码。第二类是字段长度超限pymysql.err.DataError: (1406, Data too long for column title at row 1)某些标题特别长VARCHAR(255)存不下。把字段类型改为TEXT就好。我这张表设计定为VARCHAR(255)实测大多数标题都在 100 字以内所以暂时没问题。如果你要爬长尾标题较多的站点建议直接把标题设为TEXT。第三类是ON DUPLICATE KEY UPDATE写错字段名。pymysql.err.ProgrammingError: (1054, Unknown column xxx in field list)去数据库里DESC xinpianchang_videos;看真实字段名把 SQL 对齐。7.4 完整性校验如何确认 500 条数据真的抓全了跑完之后进入 MySQLSELECT COUNT(*) FROM xinpianchang_videos; SELECT page_no, COUNT(*) FROM xinpianchang_videos GROUP BY page_no ORDER BY page_no;如果每个页码的数量大约都在 24 上下说明没有大面积漏页。再抽查几条数据的字段完整性SELECT title, play_count, like_count FROM xinpianchang_videos LIMIT 10;如果发现大量play_count 0有可能是列表页经过懒加载后播放量节点不是在页面初始 HTML 中渲染的。新片场这块实测是有的因为很多卡片数值在服务端已经拼好。不过如果你爬别的视频平台可能要处理接口响应的 JSON而不是 HTML 形如这样的字段。另外我还习惯对去重效果做验证SELECT video_url, COUNT(*) AS cnt FROM xinpianchang_videos GROUP BY video_url HAVING cnt 1;查询结果为空说明唯一索引和ON DUPLICATE KEY UPDATE配合正常。7.5 日志级别的调整如果只想减少输出可以在settings.py设LOG_LEVEL INFO开发调试阶段我会临时把级别降到DEBUG看更详细的请求响应记录。线上跑批任务时再调回INFO避免日志文件膨胀过快。8. 爬取策略的反思与后续扩展思路8.1 这个项目还能怎么扩展前 20 页列表页数据入库之后后续方向可以有这些方向一详情页扩展解析从video_url发起第二次请求解析详情页里的视频描述、标签、所属栏目、导演、出品方等更丰富的字段。新片场详情页同样以服务端渲染为主XPath 和现有代码能完全复用。方向二封面图批量下载数据库中已经有cover_url写一个独立的图片下载脚本用scrapy.Request下载封面文件到本地目录文件名用作品 ID 或随机 UUID 命名避免重名。封面图可以做视频风格研究、动态相册展示等。方向三定时增量采集把total_pages改成某时间段内更新的页数结合created_date分析增量区间在start_requests里过滤掉旧数据。也可以用 Scrapy 的Cron部署定期抓取指定页数配合ON DUPLICATE KEY UPDATE做幂等更新。方向四数据分析可视化数据落库只是第一步。拿 MySQL 里的 500 条数据做统计——作者 Top 10、播放量分布、标题词频、时长分布再交给 pandas 和 pyecharts 出图表。这个方向对爬虫学习者的综合能力提升很大也算是对爬下来的数据到底能干嘛这个问题的最好回答。8.2 关于反爬与合规边界新片场目前对低频爬取比较宽松但我还是想说清楚爬虫的目标是公共信息采集不是薅资源。我们抓的是列表页展示的标题、链接、播放量这类公开元数据不碰用户隐私、不下载付费内容更不能用代理池高频打站。在实际工程里建议给爬虫加一个显式的custom_settings把延迟调大一点在代码注释里写清楚数据用途这样即使隔几个月自己回来看也能一眼明白当初的设计意图custom_settings { DOWNLOAD_DELAY: 1.5, CONCURRENT_REQUESTS: 4, DEFAULT_REQUEST_HEADERS: { User-Agent: Mozilla/5.0 ..., Referer: https://www.xinpianchang.com/, }, }把和站点访问频率相关的设置直接放在 Spider 类内部比放全局settings.py更清晰。8.3 从跑通到稳定的进阶思考很多新手跑通一次之后就认为大功告成但真实的爬虫项目里稳定比简单跑通更重要。稳定性意味着不因为某个页面的局部异常导致整体中断不因为重复运行导致数据膨胀不因为网络抖动导致进程崩溃这三个目标分别对应异常捕获与容错解析、唯一索引 幂等写入、请求重试机制RETRY_ENABLED默认 True但可以调大RETRY_TIMES。这些机制集齐后你会发现一个 500 条的爬虫项目和日后处理 50 万条数据的架构差异不大区别只是请求队列调度、数据库分库分表和分布式代理这些工程化手段。我个人的经验是爬虫的技能树不是会写 XPath和会调 API这么简单它是一条从网络协议、HTML/JSON 解析、数据建模、存储设计到部署运维的完整链路。拿新片场这种中小型站点做全套练习性价比非常高——页面不复杂但字段齐全数据量适中但足以测试数据库性能还有真实的反爬与去重需求。最后再分享一个实操里的小技巧跑爬虫之前先手动在 MySQL 里执行一遍建表语句再打开浏览器确认目标分页 URL 能正常访问。这两个前置检查做完一次成功的概率能提高不少。省下的时间足够你把这份代码改造出自己喜欢的扩展功能了。
RELATED

相关推荐

pyGIMLi二维ERT反演实战:从数据加载到参数调优与盲测

pyGIMLi二维ERT反演实战:从数据加载到参数调优与盲测

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 20:00:13
不同 LLM Provider 的 Tool Schema 差异怎么破?OpenClaw 的 Schema 适配思路与 TaoToken 统一 Key 实践

不同 LLM Provider 的 Tool Schema 差异怎么破?OpenClaw 的 Schema 适配思路与 TaoToken 统一 Key 实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 20:00:13
SciPy科学计算实战:从优化、信号处理到稀疏矩阵的完整指南

SciPy科学计算实战:从优化、信号处理到稀疏矩阵的完整指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📅 2026/10/15 20:00:13
MORE NEWS

更多资讯

📰

DeepAgents 实战:SubAgentMiddleware 驱动 Sub-Agent 协同演化的 Harness 拆解与 TaoToken 接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

Elastic-Job-Lite作业分片策略源码解析与调优实战

1. 从一个真实需求说起:为什么作业分片策略值得深挖做分布式定时任务这些年,我踩过最深的坑几乎都和分片有关。业务量从每天几万条涨到几百万条的时候,单机跑批开始扛不住,加机器吧,任务又重复执行;不加吧&…

📰

如何快速把 Windows 11 任务栏与开始菜单改回 Windows 10 经典界面:ExplorerPatcher 完整实战指南

如何快速把 Windows 11 任务栏与开始菜单改回 Windows 10 经典界面:ExplorerPatcher 完整实战指南 【免费下载链接】ExplorerPatcher This project aims to enhance the working environment on Windows 项目地址: https://gitcode.com/GitHub_Trending/ex/Explor…

📰

T型三电平逆变器与VSG自适应控制仿真实践

1. 项目背景与核心价值电力电子变换器在新能源发电系统中扮演着关键角色,而T型三电平逆变器因其高效率、低损耗等优势,在中大功率场合得到广泛应用。传统逆变器控制策略在并离网切换过程中常面临暂态冲击大、同步困难等问题,虚拟同步机(VSG)技…

📰

C++ 进阶第二阶段总结|STL容器全家桶、迭代器失效、STL算法、序列化入门(工程必备)

文章目录前言一、STL 整体认知二、三大类容器底层原理 选型标准序列式容器(有序存储)vector(动态数组|默认首选)deque(分段双端队列)list(双向链表)关联容器&#xff08…

📰

C#实现Ymodem协议IAP升级的底层关键点解析

简介:本资源是一套基于C#实现的Ymodem协议与嵌入式IAP在线升级融合方案,面向嵌入式开发工程师、物联网固件维护人员及学习串行通信与设备升级技术的中高级开发者,解决嵌入式设备在无网络或受限环境下通过串口安全可靠升级固件的核心问题。压缩…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬