ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Scrapling 爬虫系统入门实战:从 Spider 类定义到并发抓取、链接跟踪与数据导出

Scrapling 爬虫系统入门实战:从 Spider 类定义到并发抓取、链接跟踪与数据导出 Scrapling 爬虫系统入门实战从 Spider 类定义到并发抓取、链接跟踪与数据导出【免费下载链接】Scrapling️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!项目地址: https://gitcode.com/GitHub_Trending/sc/Scrapling本文围绕 Scrapling 的 spiders 子系统入门指南展开讲解如何用最少的代码定义一个可并发运行的 Spider包括name/start_urls/parse()三要素、start()的运行机制与CrawlResult统计结果、response.follow()的多页链接跟踪、ItemList的 JSON/JSONL/CSV/XML 导出、allowed_domains域过滤与robots_txt_obey合规抓取并结合开源仓库源码说明其调度、去重与限速的底层实现。读完后你可以独立编写、运行并导出一个完整的多页面爬虫并理解每个配置项在引擎内部是如何生效的。前置知识在动手之前建议先熟悉三个基础页面Fetchers 基础了解不同 fetcher 类型静态 HTTP、动态浏览器、隐身浏览器及各自适用场景Main classes理解Selector选择器与Response对象的用法Architecture对 Spider 系统的整体工作方式有一个高层认识。Spider 系统的目标是让你用几行代码构建并发、多页面的爬虫。用过 Scrapy 的开发者会觉得很眼熟——start_urls、parse、response.follow这些模式几乎一致没用过也没有关系本文会覆盖从定义到运行的全部细节。第一个 Spider三要素一个 Spider 是一个类定义了如何从网站抓取并提取数据。最简单的 Spider 如下from scrapling.spiders import Spider, Response class QuotesSpider(Spider): name quotes start_urls [https://quotes.toscrape.com] async def parse(self, response: Response): for quote in response.css(div.quote): yield { text: quote.css(span.text::text).get(), author: quote.css(small.author::text).get(), }每个 Spider 必须具备三样东西name—— Spider 的唯一标识符。从 Spider 基类 的__init__看若name为None会直接抛出ValueError并且日志器会以scrapling.spiders.{name}命名便于多 Spider 并行时区分日志。start_urls—— 起始爬取 URL 列表。默认实现 start_requests() 会为每个 URL 生成一个Request并绑定默认会话若start_urls为空且没有覆写start_requests()引擎会抛出RuntimeError提示没有起点。parse()—— 一个异步生成器方法处理每个响应并通过yield输出结果。它继承自 基类中的抽象方法子类不实现会直接NotImplementedError。parse()是核心所在它使用与 Scrapling 的Selector/Response完全相同的选取方法yield出的字典就是被抓取的数据项。从引擎的 _run_callbacks() 看回调yield的内容只接受三种类型dict数据项会先经过on_scraped_item钩子处理、Request后续请求或None返回其他类型会记录一条 error 日志。运行 Spiderstart() 与 CrawlResult运行 Spider 只需实例化后调用start()result QuotesSpider().start()start()方法内部处理了所有异步机制无需关心事件循环。从 start() 的源码 可以看到它通过anyio.run在 asyncio 后端上运行内部__run()协程并支持以下行为日志爬虫运行期间所有事件都会输出到终端格式由logging_format控制默认带时间戳和 Spider 名爬取结束时会在终端打印一份非常详细的统计 JSON。优雅暂停start()会注册 SIGINT 处理器——按一次 CtrlC 请求优雅暂停等待在途请求完成若配置了crawldir还会保存检查点以便之后恢复再按一次则强制立即停止。可选参数start(use_uvloopTrue)可在可用时切换为更快的 uvloop/winloop 事件循环backend_options会透传给anyio.run。爬取结束后所有信息都封装在返回的CrawlResult对象中result QuotesSpider().start() # 访问抓取到的数据项 for item in result.items: print(item[text], -, item[author]) # 查看统计 print(fScraped {result.stats.items_scraped} items) print(fMade {result.stats.requests_count} requests) print(fTook {result.stats.elapsed_seconds:.1f} seconds) # 爬虫是正常跑完还是中途暂停了 print(fCompleted: {result.completed})CrawlResult的结构很简单statsCrawlStats数据类、itemsItemList、paused是否被暂停completed 属性 即not paused另外还支持len(result)和for item in result直接迭代数据项。CrawlStats的信息远比上面展示的丰富从 CrawlStats 定义 看它还包括字段含义requests_count/requests_per_second请求总数与平均速率failed_requests_count失败的请求数offsite_requests_count被域过滤丢弃的站外请求数robots_disallowed_count被 robots.txt 禁止的请求数cache_hits/cache_misses开发模式下响应缓存命中/未命中数blocked_requests_count被判定为拦截的请求数response_status_count按状态码分类的响应计数如status_200response_bytes/domains_response_bytes总字节数与按域分解的字节数sessions_requests_count按会话 ID 分解的请求数log_levels_counter各日志级别的消息条数autothrottle_delaysAutoThrottle 运行时为各域计算出的延迟这些数字正是诊断爬虫行为的窗口offsite_requests_count偏高说明链接跟丢太多blocked_requests_count偏高说明需要代理或降速。跟踪链接response.follow()大多数爬取需要跨页面跟踪链接。使用response.follow()创建后续请求from scrapling.spiders import Spider, Response class QuotesSpider(Spider): name quotes start_urls [https://quotes.toscrape.com] async def parse(self, response: Response): # 从当前页提取数据项 for quote in response.css(div.quote): yield { text: quote.css(span.text::text).get(), author: quote.css(small.author::text).get(), } # 跟踪下一页链接 next_page response.css(li.next a::attr(href)).get() if next_page: yield response.follow(next_page, callbackself.parse)response.follow()会自动处理相对 URL通过与当前页 URL 拼接完成解析默认还会把当前页设置为新请求的Referer头。从 follow() 的实现 可以看到更多细节urlself.urljoin(url)完成相对地址解析referer_flowTrue默认时会写入headers[referer]HTTP 会话或extra_headers[referer]浏览器会话并把google_search置为False未显式传入的sid、callback、priority、meta都会继承上一个请求的对应值——也就是说翻页请求默认继续用同一会话、同一回调、同一优先级meta是合并新值优先而非覆盖除dont_filter外新的**kwargs会与上一请求的会话参数合并新值优先因此可以只在某一次 follow 时覆盖proxy、headers等参数。follow()的完整签名是follow(url, sid, callbackNone, priorityNone, dont_filterFalse, metaNone, referer_flowTrue, **kwargs)。dont_filterTrue可以绕过调度器对该 URL 的去重见下文。你还可以把后续请求指向不同的回调方法以区分不同类型的页面async def parse(self, response: Response): for link in response.css(a.product-link::attr(href)).getall(): yield response.follow(link, callbackself.parse_product) async def parse_product(self, response: Response): yield { name: response.css(h1::text).get(), price: response.css(.price::text).get(), }注意所有回调方法都必须是异步生成器使用async def配合yield因为引擎是以async for方式逐个消费回调产出的。从源码结构看follow 产出的Request进入 Scheduler——一个基于asyncio.PriorityQueue的优先级队列入队时先计算请求指纹做去重dont_filterTrue可豁免priority数值越大越先出队同一指纹的重复请求会被静默丢弃并记一条 debug 日志。请求指纹的计算规则在 Request.update_fingerprint() 中基于归一化 URL、HTTP 方法、请求体和会话 ID可通过 Spider 的fp_include_kwargs/fp_include_headers/fp_keep_fragments三个开关让指纹进一步纳入其他参数、请求头或 URL 片段。导出数据ItemList 的四种格式result.items是一个带导出能力的ItemList本质是list的子类内置四种导出方法result QuotesSpider().start() # 导出为 JSON result.items.to_json(quotes.json) # 导出为带缩进的 JSON result.items.to_json(quotes.json, indentTrue) # 导出为 JSON Lines每行一个 JSON 对象 result.items.to_jsonl(quotes.jsonl) # 导出为 CSV 或 XML result.items.to_csv(quotes.csv) result.items.to_xml(quotes.xml)它们都会自动创建不存在的父目录。各方法的参数与行为细节可从 ItemList 源码 确认to_json(path, *, indentFalse)基于 orjson 序列化indentTrue使用 2 空格美化源码注释说明会稍慢to_jsonl(path)每行一个 JSON 对象适合流式处理和大数据量场景to_csv(path, *, fieldsNone, delimiter,)会为所有数据项中出现过的每个 key 生成一列key 不一致的项缺失单元格留空传fields[...]可自选列与列顺序delimiter\t即可得到 TSV 文件to_xml(path, *, root_tagitems, item_tagitem, indentTrue)每个 item 包裹在item元素中整体再包在items根元素里两个标签名均可通过root_tag、item_tag重命名不是合法 XML 名称的 key 会被重写为合法标签并把原名保留在name属性中。CSV 和 XML 两种格式中非简单标量值嵌套字典或列表会写成 JSON 字符串确保数据不会被静默丢弃——这是 _stringify() 辅助函数 的行为。此外每次导出都会通过日志记录Saved N items to path方便核对导出条数与stats.items_scraped是否一致。过滤域名allowed_domains使用allowed_domains可以把 Spider 限制在特定域内防止意外跟随到外部网站class MySpider(Spider): name my_spider start_urls [https://example.com] allowed_domains {example.com} async def parse(self, response: Response): for link in response.css(a::attr(href)).getall(): # 指向其他域名的链接会被静默丢弃 yield response.follow(link, callbackself.parse)子域名会被自动匹配设置allowed_domains {example.com}后sub.example.com、blog.example.com等同样允许通过。从 引擎的 _is_domain_allowed() 看判定逻辑就是domain allowed or domain.endswith(. allowed)若allowed_domains为空集则所有域名都放行。被过滤掉的请求会计入stats.offsite_requests_count并记录一条 debug 日志让你知道有多少链接被丢弃——这个计数对评估站点链接结构很有参考价值。过滤发生在回调产出Request之后、入队之前见 _run_callbacks()所以被丢弃的请求不会占用调度器资源。遵守 robots.txtrobots_txt_obey设置robots_txt_obey True可以让 Spider 在爬取任何域之前先遵守 robots.txt 规则class PoliteSpider(Spider): name polite start_urls [https://example.com] robots_txt_obey True async def parse(self, response: Response): for link in response.css(a::attr(href)).getall(): yield response.follow(link, callbackself.parse)开启后Spider 会预取 robots.txt爬取开始前并发抓取start_urls中所有域的 robots.txt。从 _prefetch_robots_txt() 看它会按 netloc 去重并保留首个 URL 的 scheme再交给RobotsTxtManager.prefetch()并发预取。逐个检查请求每个请求先与所属域的 robots.txtDisallow规则比对被禁止的请求静默丢弃并计入stats.robots_disallowed_count见 _process_request()。遵守Crawl-delay与Request-rate指令取指令值与配置的download_delay中的最大值。也就是说 robots.txt 的延迟只会增大你配置的延迟、绝不会减小它。Request-rate如5/30s会被换算为period / req_count后参与比较见 _get_domain_delay()且每个域的最终延迟只计算一次并缓存。robots.txt 使用 Spider 的默认会话抓取并在整个爬取期间按域缓存爬取中途才发现的域不在start_urls中会在首次请求该域时现场抓取其 robots.txt。注意robots_txt_obey默认关闭以避免意外的行为变化。启用它不会影响并发设置concurrent_requests、concurrent_requests_per_domain——被调整的只有请求之间的延迟。如果不想手动挑download_delay可以设置autothrottle_enabled TrueSpider 会根据每个域的响应快慢自行调节延迟在被拦截时自动退避相关参数autothrottle_start_delay、autothrottle_max_delay等在 Spider 类 中定义默认起步延迟 5 秒、上限 60 秒。详见 AutoThrottle 章节。与调度引擎的衔接上面各配置项最终都汇聚到 CrawlerEngine。初始化时引擎根据 Spider 配置创建对应组件robots_txt_obey开启则实例化RobotsTxtManagerautothrottle_enabled开启则创建AutoThrottledevelopment_mode开启则启用磁盘响应缓存默认目录.scrapling_cache/{name}。主循环crawl()每轮从调度器取请求并发任务同时受concurrent_requests上限约束空闲时轮询暂停标记与检查点时机结束前会打印完整的stats.to_dict()JSON。此外还有两个与合规和稳定性直接相关的内置机制源码中可直接验证拦截重试当is_blocked(response)判定为拦截默认基于状态码集合BLOCKED_CODES {401, 403, 407, 429, 444, 500, 502, 503, 504}可覆写时引擎会复制请求、递增_retry_count、降低优先级并移除代理配置后重新入队最多重试max_blocked_retries默认 3次生命周期钩子on_start(resuming)、on_close()、on_error(request, error)、on_scraped_item(item)返回None可静默丢弃该条数据计入items_dropped都可按需覆写。这些进阶能力优先级、去重、会话、代理、检查点、流式输出等在文档中有专门章节Requests Responses —— 请求优先级、去重、元数据等Sessions —— 在单个 Spider 中混用多种 fetcherHTTP、浏览器、隐身Proxy management blocking —— 跨请求轮换代理与拦截处理Advanced features —— 并发控制、AutoThrottle、暂停/恢复、流式输出、生命周期钩子与日志。【免费下载链接】Scrapling️ An adaptive Web Scraping framework that handles everything from a single request to a full-scale crawl!项目地址: https://gitcode.com/GitHub_Trending/sc/Scrapling创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表