ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

手把手教学如何采集动物收容与领养机构公开目录数据,一文讲透!

手把手教学如何采集动物收容与领养机构公开目录数据,一文讲透! ㊗️本期内容已收录至专栏《Python爬虫实战》持续完善知识体系与项目实战建议先订阅收藏后续查阅更方便㊙️本期爬虫难度指数⭐⭐⭐⭐☆高级福利一次订阅后专栏内的所有文章可永久免费看持续更新中保底1000(篇)硬核实战内容。全文目录 开篇语0️⃣ 前言Preface1️⃣ 摘要Abstract2️⃣ 背景与需求Why3️⃣ 合规与注意事项必写4️⃣ 技术选型与整体流程What/How5️⃣ 环境准备与依赖安装可复现6️⃣ 核心实现请求层Fetcher7️⃣ 核心实现解析层Parser8️⃣ 数据存储与导出Storage9️⃣ 运行方式与结果展示必写 常见问题与排错强烈建议写1️⃣1️⃣ 进阶优化可选但加分1️⃣2️⃣ 总结与延伸阅读 文末✅ 专栏持续更新中建议收藏 订阅✅ 互动征集✅ 免责声明 开篇语哈喽各位小伙伴们你们好呀我是【喵手】。运营社区 C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO欢迎大家常来逛逛一起学习一起进步我长期专注Python 爬虫工程化实战主理专栏 《Python爬虫实战》从采集策略到反爬对抗从数据清洗到分布式调度持续输出可复用的方法论与可落地案例。内容主打一个“能跑、能用、能扩展”让数据价值真正做到——抓得到、洗得净、用得上。专栏食用指南建议收藏✅ 入门基础环境搭建 / 请求与解析 / 数据落库✅ 进阶提升登录鉴权 / 动态渲染 / 反爬对抗✅ 工程实战异步并发 / 分布式调度 / 监控与容错✅ 项目落地数据治理 / 可视化分析 / 场景化应用专栏推广时间如果你想系统学爬虫而不是碎片化东拼西凑欢迎订阅专栏《Python爬虫实战》一次订阅后专栏内的所有文章可永久免费阅读持续更新中。订阅后更新会优先推送按目录学习更高效0️⃣ 前言Preface在这篇文章中我将手把手带你使用 Python 及requestslxml工具链编写一个健壮、优雅且合规的网络爬虫最终产出一份高质量的纯英文命名的animal_shelters_data.csv文件。读完本文你将获得一套可以直接拿到生产环境中使用的、具备重试机制、反屏蔽和优雅解析的工业级爬虫架构。深刻理解网络爬虫的边界与合规性学会在保护隐私的前提下高效聚合公共数据。掌握应对网页结构突变、网络超时、乱码等爬虫常见“疑难杂症”的排错方法论。1️⃣ 摘要Abstract本文旨在构建一个面向动物收容与领养机构公开信息的数据采集系统。通过 Python 的requests库处理网络请求结合lxml使用 XPath 进行 DOM 树解析最终将机构名、类型、服务地区等结构化数据导出为本地 CSV 文件。读完本文你将获得从 0 到 1 的全流程实战代码含 Fetcher 请求层、Parser 解析层、Storage 存储层解耦。掌握数据清洗与去重策略确保最终产出的数据高可用。获得进阶并发采集与反反爬优化的技术视野。2️⃣ 背景与需求Why为什么要爬这些数据目前很多城市的动物救助站、民间收容所和流浪动物领养机构的信息高度碎片化。它们散落在各种地方论坛、社交媒体甚至是非常古老的黄页网站上。通过信息聚合我们可以建立一个统一的导航目录或地图帮助想要领养宠物的人快速找到离自己最近的机构。这不仅仅是一个数据分析的项目更是一个用技术传递善意的自动化信息匹配系统。我们的目标字段Fields 清单基于需求我们明确提取以下核心结构化字段。请注意本项目的核心逻辑是**“对事不对人”**。agency_name(机构名)如“阳光流浪狗救助基地”。agency_type(机构类型)如“政府收容所”、“民间非营利组织(NGO)”、“个人救助站”。service_area(服务地区)如“北京市朝阳区”。open_hours(开放时间)如“周一至周五 09:00-17:00”。status(服务状态)如“正常开放”、“暂停接收”、“即将关闭”。link(链接)该机构的官方网址或详情页 URL用于溯源。3️⃣ 合规与注意事项必写作为一名有职业素养的爬虫工程师我们必须把“规矩”写在前面。技术是中立的但使用技术的人要有敬畏之心。Robots.txt 协议先行在请求任何网站之前老手的第一步永远是在域名后加上/robots.txt。这就像是去别人家拜访前先看门牌上的注意事项。如果网站声明了Disallow: /shelters/那么在没有获得授权的情况下我们应当停止采集该目录。克制并发不要“攻击式”抓取很多公益机构的网站服务器配置非常低可能就是一台丐版云服务器。如果你为了炫技一上来就开 100 个线程并发狂扫很容易把对方服务器打挂DDoS。这不叫爬虫这叫网络攻击。我们必须加入随机休眠time.sleep做好频率控制Rate Limiting。坚守隐私底线核心重点本项目的实战重点是“仅采集机构公开信息”。如果在页面上看到了领养申请人的姓名、电话、家庭住址或者发布流浪狗信息的普通市民的微信号坚决不要采集、不要解析、不要存储。我们不绕过任何付费墙也不通过撞库去获取登录后的私密数据。用最中立、最克制的态度对待互联网公开数据。4️⃣ 技术选型与整体流程What/How技术流派分析目前主流爬虫分三种静态页面抓取Static服务端直接渲染好 HTML 返回如传统的新闻站、黄页。动态渲染抓取Dynamic数据由前端 JavaScript 异步加载Ajax/Vue/React或者页面有极其复杂的 JS 加密需要用 Playwright/Selenium 模拟浏览器。API 抓取直接抓包找到了返回 JSON 数据的后端接口。我们的选择这篇教程我将带你攻克最经典、最基础也最扎实的静态页面抓取Static。为什么不直接教 Playwright因为对于新手不理解 HTTP 协议和 DOM 树直接用自动化测试工具是一种“降维依赖”遇到性能瓶颈时会束手无策。工具链网络请求requests最人性化的 HTTP 库。HTML 解析lxml配合 XPath。为什么不用 BeautifulSoup (bs4)个人经验当你处理上万个页面时底层基于 C 语言的lxml的解析速度会把纯 Python 实现的解析器按在地上摩擦且 XPath 的定位能力极其强悍。数据流转流程[初始 URL (机构列表页)] ↓ 【Fetcher 请求层】 - 伪装 Headers - 发起 GET 请求 - 处理异常重试 - 返回 HTML 文本 ↓ 【Parser 解析层】 - lxml 加载 HTML - XPath 提取列表所有的详情页 URL ↓ (循环请求每个详情页) 【Fetcher 请求层】 - 拿到详情页 HTML ↓ 【Parser 解析层】 - 提取机构名、类型、地区、时间、状态 - 容错处理 (缺失补 None) ↓ 【Cleaner 清洗层】 - 去除两端空格、清洗异常字符 - 组装成字典 ↓ 【Storage 存储层】 - Pandas DataFrame - 去重处理 - 导出为 CSV 文件5️⃣ 环境准备与依赖安装可复现工欲善其事必先利其器。请确保你的电脑上安装了Python 3.8 或更高版本推荐 3.10因为我会使用到一些类型提示 Type Hints这会让代码看起来非常专业。打开你的终端Terminal或命令行CMD执行以下命令安装依赖pipinstallrequests lxml pandas fake-useragentrequests: 核心 HTTP 引擎。lxml: 高性能 HTML/XML 解析引擎。pandas: 数据处理及 CSV 导出的王者。fake-useragent: 随机生成浏览器指纹User-Agent防止被基础反爬识别。推荐的项目结构创建一个文件夹叫AnimalShelterSpider在里面建立如下结构AnimalShelterSpider/ │ ├── spider_core.py # 核心业务逻辑我们将要把代码写在这里 ├── utils.py # 存放一些辅助函数选做 └── data/ # 爬取下来的数据存放在这里需手动创建6️⃣ 核心实现请求层Fetcher我们要写一个健壮的下载器。绝不能简单地写一句requests.get(url)就完事了网络波动、超时、服务器拒绝都会导致程序崩溃。在这个Fetcher类中我会引入requests.Session()来保持连接池并使用重试机制退避策略思路。importrequestsimporttimeimportrandomimportloggingfromfake_useragentimportUserAgentfromtypingimportOptional# 配置简单的日志输出logging.basicConfig(levellogging.INFO,format%(asctime)s - %(levelname)s - %(message)s)classFetcher:def__init__(self):self.sessionrequests.Session()self.uaUserAgent()self.timeout15# 设置统一超时时间 15 秒defget_headers(self)-dict:每次请求动态生成 Headers伪装成正常浏览器return{User-Agent:self.ua.random,Accept:text/html,application/xhtmlxml,application/xml;q0.9,image/webp,*/*;q0.8,Accept-Language:zh-CN,zh;q0.9,en;q0.8,Connection:keep-alive,# 如果目标网站校验来源可以加上 Referer# Referer: https://www.google.com/}deffetch(self,url:str,max_retries:int3)-Optional[str]: 发起请求的健壮方法。包含异常捕获和指数退避重试机制。 forattemptinrange(max_retries):try:# 每次请求前随机休眠保护对方服务器也保护我们自己的 IPsleep_timerandom.uniform(1.0,3.0)time.sleep(sleep_time)logging.info(f正在抓取:{url}(等待{sleep_time:.2f}s))responseself.session.get(url,headersself.get_headers(),timeoutself.timeout)# 检查 HTTP 状态码如果不是 200主动抛出异常response.raise_for_status()# 处理可能出现的编码问题response.encodingresponse.apparent_encodingreturnresponse.textexceptrequests.exceptions.RequestExceptionase:logging.warning(f请求失败 ({attempt1}/{max_retries}):{url}. 错误:{e})ifattemptmax_retries-1:logging.error(f最终彻底失败放弃当前 URL:{url})returnNone# 退避策略每次失败后等待时间翻倍防止越错越刷time.sleep(2**attempt)returnNone技术亮点解析Session使用requests.Session()比直接调用底层的 HTTP 握手效率更高它会自动维持底层的 TCP 连接Keep-Alive处理批量请求时速度明显提升。Timeout必须设置你不设置遇到死链接爬虫就会无限期挂起假死。容错与退避time.sleep(2 ** attempt)。第一次重试等 1 秒第二次等 2 秒第三次等 4 秒。这是一种非常绅士且高效的重试逻辑。7️⃣ 核心实现解析层Parser拿到 HTML 源码后我们要用lxml将其转换为 DOM 树然后用 XPath 去精确定位目标数据。这里我假设一个通用网站的结构列表页有一个div classshelter-item里面有个a标签指向详情页。详情页里有各种标签存放具体信息。fromlxmlimportetreeimportreclassParser:def__init__(self):passdefparse_list_page(self,html_content:str)-list: 解析列表页提取出所有详情页的 URL ifnothtml_content:return[]treeetree.HTML(html_content)# 假设列表页中每个收容所的链接在 class 为 shelter-link 的 a 标签里# //a[classshelter-link]/href 意思是获取全文档所有符合该class的a标签的href属性detail_urlstree.xpath(//a[contains(class, shelter-link)]/href)# 补全可能为相对路径的 URL (这里假设域名是 example.com实际中需根据情况处理)base_urlhttps://www.example-shelter-directory.comfull_urls[base_urlurlifurl.startswith(/)elseurlforurlindetail_urls]# URL 去重后返回returnlist(set(full_urls))defclean_text(self,text_list:list)-str: 清洗 XPath 提取出来的文本内容列表 ifnottext_list:returnN/A# 缺失字段容错赋予默认值# 取第一个元素去掉两端空白字符去除可能含有的换行符等raw_textstr(text_list[0])clean_strre.sub(r\s, ,raw_text).strip()returnclean_strifclean_strelseN/Adefparse_detail_page(self,html_content:str,current_url:str)-dict: 抽丝剥茧提取详情页的指定字段 ifnothtml_content:return{}treeetree.HTML(html_content)# 注意这里的 XPath 路径是假设的你需要根据真实网站的 F12 元素结构进行替换# 使用 contains 等函数能提高 XPath 的鲁棒性防止类名发生微小改变导致崩溃agency_nametree.xpath(//h1[classagency-title]/text())agency_typetree.xpath(//span[contains(text(), 机构类型)]/following-sibling::span/text())service_areatree.xpath(//div[idlocation-info]/p/text())open_hourstree.xpath(//i[contains(class, icon-clock)]/parent::div/span/text())statustree.xpath(//div[classstatus-badge]/text())data{agency_name:self.clean_text(agency_name),agency_type:self.clean_text(agency_type),service_area:self.clean_text(service_area),open_hours:self.clean_text(open_hours),status:self.clean_text(status),link:current_url}returndata老手经验分享缺失字段怎么办你绝不能假设每个详情页都有“开放时间”这个标签。一旦某个页面少了该标签XPath 返回的就是空列表[]。如果不做判断直接取agency_type[0]程序立刻抛出IndexError而崩溃。在上面的clean_text方法中我做了严格的if not text_list:判断如果抓不到优雅地返回N/A(Not Available)保证程序的连续性。这就是容错Fault Tolerance。8️⃣ 数据存储与导出Storage采集完的数据在内存中是一堆字典Dict。我们需要把它持久化。对于百兆级别以内的数据保存为 CSV 是性价比最高的选择。我们使用pandas来处理。importpandasaspdimportosclassStorage:def__init__(self,output_dir:strdata):self.output_diroutput_dirifnotos.path.exists(self.output_dir):os.makedirs(self.output_dir)# 强制使用纯英文文件名self.filenameos.path.join(self.output_dir,shelter_directory_export.csv)self.data_buffer[]defappend_data(self,data_dict:dict):将清洗后的字典存入缓存列表ifdata_dict:self.data_buffer.append(data_dict)defsave_to_csv(self):将缓存区的数据保存至硬盘ifnotself.data_buffer:logging.warning(没有数据可保存)returndfpd.DataFrame(self.data_buffer)# 数据去重策略基于 URL 唯一性去重 (保留最后抓取的记录)original_countlen(df)df.drop_duplicates(subset[link],keeplast,inplaceTrue)final_countlen(df)iforiginal_count!final_count:logging.info(f已清理重复数据:{original_count-final_count}条。)# 导出为 CSV指定 utf-8-sig 编码防止 Excel 打开乱码df.to_csv(self.filename,indexFalse,encodingutf-8-sig)logging.info(f数据成功导出至:{self.filename}共计{final_count}条有效记录。)9️⃣ 运行方式与结果展示必写现在我们要把所有模块串联起来写一个主函数入口。假设我们要抓取某个黄页的 1 到 5 页数据。defmain():print( 动物收容与领养机构数据采集引擎启动...)fetcherFetcher()parserParser()storageStorage()# 模拟分页采集逻辑base_list_urlhttps://www.example-shelter-directory.com/list?pagetotal_pages2# 示例仅采集2页forpageinrange(1,total_pages1):list_urlf{base_list_url}{page}logging.info(f--- 正在解析第{page}页列表 ---)# 1. 获取列表页 HTMLlist_htmlfetcher.fetch(list_url)# 2. 解析列表页拿到详情页 URLsdetail_urlsparser.parse_list_page(list_html)logging.info(f第{page}页发现{len(detail_urls)}个机构...)# 3. 遍历请求详情页forurlindetail_urls:detail_htmlfetcher.fetch(url)# 4. 解析详情页字段shelter_dataparser.parse_detail_page(detail_html,url)# 5. 存入内存storage.append_data(shelter_data)# 6. 全部执行完毕落盘持久化storage.save_to_csv()print(✅ 采集任务圆满完成)if__name____main__:main()如何启动打开终端确保所在目录正确输入python spider_core.py结果展示示例 CSVshelter_directory_export.csv内容结构agency_nameagency_typeservice_areaopen_hoursstatuslink爱心之家流浪动物救助民间非营利组织上海市浦东新区09:00 - 18:00正常开放https://…/id/102市立宠物收容管理所政府收容所广州市天河区10:00 - 16:30N/Ahttps://…/id/205毛孩子的中转站个人救助站成都市武侯区全天需预约暂停接收https://…/id/311………………(注由于我们做了容错处理即使广州市的那家机构没有写状态也会自动填充N/A而不是留空或报错。) 常见问题与排错强烈建议写在你把上面那套代码对准真实目标网站时100% 会遇到各种奇葩问题。不要慌这些都是我当年踩过的坑现在把经验传授给你返回状态码 403 (Forbidden) 或 429 (Too Many Requests) 怎么办原因分析403 意味着服务器认出你是爬虫并把你拉黑了429 意味着你请求太快了触发了对方的频控防火墙。排错方案首先检查User-Agent是否生效。有些苛刻的网站需要校验Referer和Cookie。可以在请求头上加上一个固定的普通用户 Cookie。如果还是被封说明 IP 被限制了这时候就需要引入代理 IP 池 (Proxy Pool)在requests.get中加入proxies{http: http://ip:port, https: http://ip:port}参数。并大幅提高time.sleep的时长。成功获取 200 状态码但 HTML 里是“空壳”找不到想要的数据原因分析这个极度常见老手都知道这说明目标网站是动态渲染的。数据不在初始的 HTML 里而是页面加载后通过 JavaScript 发起 Ajax 请求获取的或者数据被写在了一段带有加密的script标签里。排错方案按 F12 打开浏览器的“开发者工具”切换到Network (网络)面板选择Fetch/XHR。然后刷新页面去观察那个包含了真实机构列表数据的 JSON 接口。一旦找到了直接用requests去请求那个 JSON 接口不仅速度快 10 倍连解析 HTML 的功夫都省了。XPath 解析报错 / 抓不到数据原因分析很多时候是因为网页结构是不稳定的比如有的网页多加了一个div绝对路径就失效了。排错方案永远、永远不要使用浏览器右键自动生成的Copy XPath那种又臭又长全是/div[3]/div[1]/ul/li[4]的路径。要使用基于属性的相对路径例如我代码中演示的//div[classstatus-badge]只要这个特征值还在外围的 HTML 怎么改都不会影响你抓取。抓下来的中文变成一堆乱码如ä½ å¥½原因分析编码错误。网页实际使用的是GBK而 requests 默认猜成了ISO-8859-1。排错方案查看网页源码头部的meta charsetxxx。手动强制赋值response.encoding utf-8或response.encoding gbk。在我上面的代码中使用了response.encoding response.apparent_encoding这一招非常管用它会分析内容来智能推测编码。1️⃣1️⃣ 进阶优化可选但加分当你的采集目标从 100 家变成了 100,000 家时上述的单线程脚本抓取几天几夜都抓不完。这时你需要掌握进阶技巧提速并发抓取Concurrent Programming不想使用笨重的框架Python 内置的concurrent.futures.ThreadPoolExecutor是极好的选择。因为网络请求是 IO 密集型操作多线程能极大地榨干带宽。但请牢记合规与善意针对公益性收容所站点请将线程池max_workers控制在3到5即可。稳健断点续跑Resumable Scraping抓到 8000 条时由于断网程序崩了重头再来太崩溃了。我们可以利用一个本地的set或者简单的 SQLite 数据库或者 Redis维护一个布隆过滤器 (Bloom Filter)或已抓取 URL 集合。每次发起请求前if url in url_pool: continue。工程化框架迁移Scrapy如果你的项目发展成为一个需要每天定时全网扫描更新的大型系统纯手写代码就显得吃力了。你需要将上述逻辑无缝迁移到Scrapy框架。Scrapy 帮你做好了并发调度、去重、数据管道Pipeline甚至它内建的容错机制比我们自己写的更完善。1️⃣2️⃣ 总结与延伸阅读到这里我们共同构建了一个小巧但五脏俱全的数据采集引擎。让我们复盘一下我们明确了只针对公开的非隐私信息下手保证了合规性我们利用requests与Session构建了具备重试退避机制的 Fetcher我们用lxml的 XPath 优雅地抽离出机构名称、类型和联系方式等核心字段并做了极致的容错最后我们用pandas清洗去重将数据稳稳落盘为纯英文命名的文件。下一步可以做什么如果你对这方面充满热情我建议你去了解一下Playwright。如果有一天你遇到某个收容所网站里面有非常复杂的 JS 验证比如让人点击图片里的斑马线用我们今天讲的纯requests方式就会遭遇瓶颈。Playwright 能够帮你驱动一个真实的无头浏览器几乎能模拟人类在网页上的任何操作。最后我想再次对你说用技术的力量去构建一个“流浪动物收容所导航地图”真的非常棒你正在让这个世界变得更加美好一点点。如果在后续的编码中遇到任何奇奇怪怪的 Bug请记得深呼吸看 Log找原因。期待你能用这套爬虫代码产出极具价值的数据项目。祝你 Coding 愉快早日大功告成 文末好啦以上就是本期的全部内容啦如果你在实践过程中遇到任何疑问欢迎在评论区留言交流我看到都会尽量回复咱们下期见小伙伴们在批阅的过程中如果觉得文章不错欢迎点赞、收藏、关注哦三连就是对我写作道路上最好的鼓励与支持❤️✅ 专栏持续更新中建议收藏 订阅墙裂推荐订阅专栏 《Python爬虫实战》本专栏秉承着以“入门 → 进阶 → 工程化 → 项目落地”的路线持续更新争取让每一期内容都做到✅ 讲得清楚原理✅ 跑得起来代码✅ 用得上场景✅ 扛得住工程化想系统提升的小伙伴强烈建议先订阅专栏 《Python爬虫实战》再按目录大纲顺序学习效率十倍上升✅ 互动征集想让我把【某站点/某反爬/某验证码/某分布式方案】等写成某期实战评论区留言告诉我你的需求我会优先安排实现(更新)哒~⭐️ 若喜欢我就请关注我叭更新不迷路⭐️ 若对你有用就请点赞支持一下叭给我一点点动力⭐️ 若有疑问就请评论留言告诉我叭我会补坑 更新迭代✅ 免责声明本文爬虫思路、相关技术和代码仅用于学习参考对阅读本文后的进行爬虫行为的用户本作者不承担任何法律责任。使用或者参考本项目即表示您已阅读并同意以下条款合法使用 不得将本项目用于任何违法、违规或侵犯他人权益的行为包括但不限于网络攻击、诈骗、绕过身份验证、未经授权的数据抓取等。风险自负 任何因使用本项目而产生的法律责任、技术风险或经济损失由使用者自行承担项目作者不承担任何形式的责任。禁止滥用 不得将本项目用于违法牟利、黑产活动或其他不当商业用途。使用或者参考本项目即视为同意上述条款,即 “谁使用谁负责” 。如不同意请立即停止使用并删除本项目。
返回列表