Python爬虫简单运用爬取代理IP的实现

📅 发布时间:2026/10/10 13:39:08
Python爬虫简单运用爬取代理IP的实现
前言这篇讲的是页面解析的技术面给定一个内容里含有ip:port的页面怎么把地址抽出来。这是一个很典型的「结构化抽取」练习和「去哪找代理」是两个问题。标题里说「爬取代理IP」很容易让人以为重点是「找到免费代理站然后抓下来」——本文不这么做也不会给出任何真实站点的地址示例里的 HTML 全部是我自己构造的本地字符串你在自己的编辑器里就能跑。有必要把来源问题说清楚因为它直接决定这篇要不要写。公开的「免费代理」来源不可信原因不是「质量差」这么简单你根本不知道流量经过谁。一个代理的运营者可以看到你通过它发出的全部明文请求包括你带的 Cookie、Token、以及请求内容这等于把一个陌生人插进了你的请求链路。有些「免费代理」本身就是蜜罐专门用来收集谁在用代理、以及通过代理访问了什么。有些地址根本不做转发而是把请求直接发出去再回来你的真实来源 IP 就这么泄露了。还有一类会在响应里注入内容做中间人式的篡改。所以即便你只是想练手也请把练习范围限定在自己构造的页面上真要用于生产来源必须是你有权使用的、可以追溯的自建代理、公司出口、付费代理服务。本机没有 Python 解释器也没有装requests等第三方库本文代码未在本机运行验证只作逐行人工推演用到的标准库html.parser、re、ipaddress都是随 Python 一起分发的第三方库的部分会注明需要先pip install。一、先定任务边界先说清楚这篇的输入输出项内容输入一段含有地址列表的 HTML 文本本文中为本地字符串输出一个去重后的host:port字符串列表不做的事不做真实网络请求、不访问任何真实站点、不校验地址是否可用前提页面来源是你有权解析的内容这里要特别强调「不做的事」本文只到「把文本里的地址抽出来」为止。抽出来的地址一律视为不可信能不能用、要不要用是另一个必须单独决策的问题。二、用标准库html.parser解析本地 HTML标准库自带一个 HTML 解析器html.parser它的用法是继承HTMLParser重写handle_starttag、handle_data、handle_endtag这几个钩子解析器在扫到对应结构时回调你。它不需要装任何东西这是它最大的优点。先构造一段页面。这是我自己写的演示 HTML表格里混了正常地址和几个不合法的值用来检验后面抽取逻辑的健壮性# 适用于 Python 3.8DEMO_HTML htmlbodytabletrthIP/thth端口/thth位置/th/trtrtd192.0.2.10/tdtd8080/tdtd演示机房 A/td/trtrtd192.0.2.11/tdtd3128/tdtd演示机房 B/td/trtrtd198.51.100.7/tdtd8888/tdtd演示机房 C/td/trtrtd999.1.1.1/tdtd80/tdtd非法 IP应被丢弃/td/trtrtd203.0.113.9/tdtd70000/tdtd端口越界应被丢弃/td/tr/table/body/html192.0.2.0/24、198.51.100.0/24、203.0.113.0/24是 RFC 5737 专门保留给文档示例的网段不会指向真实主机——这正是你写示例时该用的地址段。接下来写解析器把表格的每一行读出来# 适用于 Python 3.8from html.parser import HTMLParserclass TableRows(HTMLParser):把表格解析成 [[单元格, 单元格, ...], ...]def __init__(self):super().__init__()self.rows []self._row Noneself._cell Nonedef handle_starttag(self, tag, attrs):if tag tr:self._row []elif tag in (td, th) and self._row is not None:self._cell []def handle_data(self, data):if self._cell is not None:self._cell.append(data)def handle_endtag(self, tag):if tag in (td, th) and self._cell is not None:if self._row is not None:self._row.append(.join(self._cell).strip())self._cell Noneelif tag tr and self._row is not None:self.rows.append(self._row)self._row Noneparser TableRows()parser.feed(DEMO_HTML) # 解析本地字符串没有任何网络请求parser.close()for row in parser.rows:print(row)这里有几个容易踩的点都在代码里处理了td出现在tr之外时self._row是None所以每次用到它都先判空handle_data可能被调用多次单元格里嵌了别的标签时文字会被拆成几段回调所以单元格内容要先收集到一个列表再join不能直接赋值覆盖strip()必须在join之后做否则拿到的可能是带换行的碎片。feed()只负责喂数据建议最后调用一次close()让解析器把缓冲里的内容处理完。三、用ipaddress校验而不是只靠正则表格只是搬运工真正的校验在抽取这一步。最直观的做法是用正则匹配ip:port# 适用于 Python 3.8import ipaddressimport reADDR_RE re.compile(r(\d{1,3}(?:\.\d{1,3}){3}):(\d{1,5}))def to_endpoint(host, port_text):校验并规范化一个 host:port非法则返回 Nonetry:ipaddress.IPv4Address(host) # 非法 IPv4 会抛 AddressValueErrorexcept ipaddress.AddressValueError:return Nonetry:port int(port_text)except ValueError:return Noneif not (1 port 65535):return Nonereturn f{host}:{port}要理解正则和校验的分工正则只负责「找出长得像的片段」ipaddress才负责判断「它是不是合法的 IPv4」。上面那个\d{1,3}允许999.1.1.1正则完全无法识别而ipaddress.IPv4Address(999.1.1.1)会直接抛AddressValueError把它挡掉。端口同理——70000能通过正则的\d{1,5}但超出1..65535的范围必须自己检查。把两段接起来从解析出的表格行里抽地址# 适用于 Python 3.8# ADDR_RE 与 to_endpoint 沿用上一段代码里的定义def extract_endpoints(rows):seen set()result []for row in rows:if len(row) 2:continue# 演示页面把 IP 和端口放在两列里先用冒号拼回 ip:port 再匹配joined :.join(row[:2])m ADDR_RE.fullmatch(joined)if not m:continueendpoint to_endpoint(m.group(1), m.group(2))if endpoint and endpoint not in seen:seen.add(endpoint)result.append(endpoint)return resultprint(extract_endpoints(parser.rows))# 预期只保留 3 条合法地址非法 IP 与越界端口各被丢一条这里用的是fullmatch而不是search因为joined已经被我们拼成了恰好两段用fullmatch要求整串都符合ip:port的形状能挡掉「IP 列里混了别的东西」的情况。如果页面里是「一格里写着1.2.3.4:8080」这种形态直接用search找子串更合适——用哪个取决于文本的形状。用set去重是必要的同一批来源里重复地址很常见不去重会让下游的校验做无用功。四、第三方解析器思路与注意事项如果你的页面结构比表格复杂嵌套层很深、有大量属性、需要按 CSS 选择器或 XPath 定位标准库的html.parser会写得很啰嗦这时通常会用lxml或BeautifulSoupbs4。它们是第三方库需要先pip install lxml/pip install beautifulsoup4本机没有安装。用它们的思路是一模一样的只是把「手写状态机」换成「选择器」定位到承载地址的那批节点取出文本再走上面同一套正则加ipaddress的校验。本机既没有这些库我也无法运行验证所以这里不写出具体到参数名的调用——find_all也好、xpath也好参数名和返回类型请以这两个库的官方文档为准不要照抄任何你没核对过的签名。还有个层次问题值得单独提醒requests也是第三方库pip install requests它负责把页面取回来。但本文的所有示例都刻意不做网络请求——把「取」和「解析」分开既方便测试解析函数可以喂本地字符串也避免让示例默认指向某个真实站点。另外如果你要解析的是 HTML 里嵌的ip:port列表还要注意不要用正则去解析整个 HTML 文档。HTML 不是正则语言一个跨标签的正则匹配很容易在复杂页面上失控回溯、误匹配、把注释里的内容也抓出来。正则只应该作用在已经由解析器取出的文本节点上这是上面流程里先解析、后正则的原因。五、抽出来之后一律当作不可信抽到地址不等于能用。至少要再走这几步且每一步都要意识到「这些地址可能本身就是陷阱」去重上面已经做了逐个校验另写一个真实探测环节探测目标必须是你自己控制、且允许被探测的服务加来源标记记录每个地址来自哪一批数据出问题时能追溯绝不把来源不明的地址用于携带 Cookie、Token 或任何身份凭据的请求——这等于把凭据交给一个陌生人。第 4 条最要紧。用免费代理去访问需要登录的站点等于把你自己的会话凭据直接送给了代理运营者。常见坑点❌ 直接对一整篇 HTML 用正则匹配ip:port跳过解析器。✅ 先用html.parser之类的解析器取出文本节点再对文本节点用正则HTML 不是正则语言。❌ 只用正则\d{1,3}(\.\d{1,3}){3}判断地址合法性于是999.1.1.1被当成有效地址。✅ 正则找候选ipaddress.IPv4Address做合法性校验端口再单独检查1..65535。❌ 在handle_data里直接self._cell data结果一段文字被拆成多次回调时只剩最后一片。✅ 先把片段收集进列表handle_endtag时再join并strip。❌ 把td标签写在tr外面代码里self._row还是None就直接append抛AttributeError。✅ 每次使用前判空self._row is not None才写入。❌ 从公开免费代理站抓地址直接拿来用还带着自己的登录 Cookie。✅ 公开免费代理可能是蜜罐或中间人绝不用来源不明的代理发送任何凭据来源必须可追溯。❌ 相信「地址在页面上出现过就是可用的」不做去重也不做校验。✅ 去重、记录来源、独立校验三步都要做抽出来只是候选不是可用资源。❌ 为了解析复杂页面凭印象写一个bs4或lxml的调用参数名都没核对。✅ 这些是第三方库需先安装接口以各自官方文档为准不确定就不要写出具体参数名。❌ 在示例里写死一个真实站点地址读者一运行就去打了别人的服务器。✅ 示例一律用具名保留网段如192.0.2.0/24和本地构造的 HTML 字符串。总结步骤用什么关键点准备输入自己构造的本地 HTML 字符串用 RFC 5737 保留网段不指向真实站点解析结构html.parser.HTMLParser处理tr/td嵌套、多次handle_data、上下文判空抽取候选re正则只对文本节点匹配不在整篇文档上跑校验ipaddress.IPv4Address 端口范围正则管「像」ipaddress管「是不是合法」收尾set去重、来源标记抽出来的地址一律视为不可信「爬取代理IP」这件事技术上的难点其实很有限——解析一段结构化的 HTML、抽字段、做校验都是基础操作。真正需要投入判断力的是来源和用途这段 HTML 从哪来、抽出的地址能不能信、拿它们去干什么。示例用本地字符串不只是因为本机没有网络环境更是因为在教学场景里让读者把注意力放在解析逻辑而不是「哪个站还能抓」上才是对的。参考html.parser、re、ipaddress的接口以 Python 官方文档为准lxml、BeautifulSoup、requests为第三方库需另行安装接口以其官方文档为准本文代码未在本机运行仅作人工推演。