资讯动态

URL后缀批量筛选去重工具:从原理到Python脚本实战

发布时间:2026/10/27 17:08:35 来源:尧图企业网站定制
简介这是一款面向需要批量处理海量网址的运维、爬虫开发与数据分析人员的实用工具由AI虎哥定制专门解决几十万乃至上百万条URL的指定后缀筛选、过滤与去重保存问题。压缩包共11个文件约66KB包含可执行程序、批处理脚本、常用与不用后缀清单、安装教程及说明文档等其中exe与bat负责核心处理逻辑txt提供后缀规则参考html与url文件用于查看更新和更多资源jpg为配套图示。已有58人学习下载。工具采用智能多线程处理技术可实现秒级响应用户可自定义保留或排除特定后缀并自动去重避免重复条目影响后续爬取与分析效率。包内附必看文件与安装教程便于快速上手适合市场分析、数据采集等场景下需要高效整理网址列表的读者参考使用。1. 后缀筛选这件事为什么手工做迟早要翻车手里攒了几万条 URL要按后缀挑出.com、.cn、.org顺手把重复的干掉——听起来是个十分钟的活。真上手就知道Excel 打开十万行就开始转圈CtrlH替换到一半发现把域名里的字符也误伤了去重时又分不清http和https、带不带www、末尾有没有斜杠算不算同一条。更别提从各种 App 分享链接里扒出来的地址长这样snssdk1128://webview?urlhttps%3a%2f%2faweme.snssdk.com%2ffalcon...里面套着一层 URL 编码直接按后缀切根本切不对。这就是「URL 网址指定后缀批量筛选处理工具」要解决的问题把一堆杂乱网址喂进去按你指定的后缀过滤同时完成去重和清洗输出一份能直接用的清单。它适合做数据清洗、爬虫结果整理、外链资产盘点、SEO 竞品域名归集的从业者。核心动作只有三个——解析、筛选、去重但每一个都有坑。下面按我实际做过的路径从原理到能跑起来的脚本一步步拆开讲。2. 先搞清楚 URL 的哪一段才叫「后缀」2.1 后缀到底指域名后缀还是路径后缀很多人说「按后缀筛选」脑子里想的其实是两件不同的事。一种是顶级域名后缀TLD比如.com、.cn、.net、.org你要的是「所有 .cn 结尾的站点」另一种是路径后缀比如.html、.php、.jpg、.zip你要的是「所有静态页或压缩包链接」。这两者的解析位置完全不同前者取的是 host 部分的最后一段后者取的是 path 部分的扩展名。如果不先分清脚本写出来就是错的。比如https://blog.example.com/article.zip按 TLD 筛.com它命中按路径后缀筛.zip它也命中但如果你把整条 URL 拿去endswith(.com)这条就漏了因为它结尾是.zip。所以第一步永远是把 URL 拆成 scheme、host、path、query 四段再决定在哪一段上做后缀匹配。常见做法是用语言自带的 URL 解析库而不是自己写正则去切。Python 里就是urllib.parse.urlparse它能稳定拆出这几段遇到畸形 URL 也不会直接崩。2.2 用 urlparse 拆解一条 URL 的最小验证先跑通一条确认拆出来的结构符合预期再批量。下面这段是验证脚本from urllib.parse import urlparse # 故意选一条带编码、带 query 的复杂 URL raw https://sub.example.com/path/page.html?id123refabc p urlparse(raw) print(scheme:, p.scheme) # https print(host :, p.hostname) # sub.example.com print(path :, p.path) # /path/page.html print(query :, p.query) # id123refabc # 取 TLDhost 按点切取最后一段 tld p.hostname.split(.)[-1] if p.hostname else print(tld :, tld) # com # 取路径后缀path 里最后一个点之后的部分 ext p.path.rsplit(., 1)[-1] if . in p.path else print(ext :, ext) # html逻辑说明urlparse把 URL 拆成命名元组hostname会自动去掉端口和大小写归一比手动split(/)可靠得多。参数说明p.hostname返回的是小写主机名p.path保留原始大小写所以做后缀比较时统一.lower()更稳。注意rsplit(., 1)只切最后一个点避免page.min.html这种被切错。提示urlparse对没有 scheme 的裸域名如example.com/a.html会把整串当成 pathhost 为空。批量处理前要先判断有没有 scheme没有就补一个http://再解析。2.3 后缀匹配用 endswith 还是正则确定好取哪一段之后匹配本身很简单str.endswith()就够。但有个细节.com会误伤.com.cn吗不会因为.com.cn的 TLD 是cn不是com。真正会误伤的是用整条 URL 做 endswith的场景比如https://a.com/download结尾不是.com但它是 .com 站点。所以匹配对象必须是拆出来的 host 或 path不是原始字符串。如果后缀规则多且要支持通配比如「所有.gov和.edu结尾」用元组传给 endswith 即可host.endswith((.gov, .edu))。正则只在需要复杂模式如「二级域名是数字」时才上日常筛选没必要正则写错反而更难排查。3. 批量筛选与去重的完整脚本怎么写3.1 读取、清洗、筛选、去重四步流水线把前面拆解的逻辑串成一条流水线。输入是一个文本文件每行一条 URL输出是筛选去重后的结果。核心顺序是先规范化再筛选最后去重。顺序不能反否则HTTP://A.com和http://a.com会被当成两条。from urllib.parse import urlparse, urlunparse def normalize(url: str) - str: 规范化补 scheme、host 转小写、去掉末尾斜杠 url url.strip() if not url: return if :// not in url: url http:// url p urlparse(url) if not p.hostname: return # 重建scheme 和 host 小写path 去掉末尾多余的 / path p.path.rstrip(/) or / return urlunparse(( p.scheme.lower(), p.hostname.lower(), path, , p.query, )) def keep_by_tld(url: str, tlds: tuple) - bool: 按顶级域名后缀筛选 host urlparse(url).hostname or return host.endswith(tlds) def process(infile: str, outfile: str, tlds(.com, .cn)): seen set() kept [] with open(infile, encodingutf-8) as f: for line in f: u normalize(line) if not u: continue if not keep_by_tld(u, tlds): continue if u in seen: # 去重靠 setO(1) continue seen.add(u) kept.append(u) with open(outfile, w, encodingutf-8) as f: f.write(\n.join(kept)) print(f输入处理完成保留 {len(kept)} 条) process(urls.txt, urls_clean.txt, tlds(.com, .cn, .org))逻辑说明normalize负责把各种写法统一成一种标准形式这是去重能生效的前提。keep_by_tld只做筛选职责单一。process里用set记录已见 URL去重是 O(1)十万条也就几秒。参数说明tlds传元组想加后缀直接往里塞normalize里path.rstrip(/) or /保证根路径统一成/避免a.com和a.com/被算成两条。3.2 处理 URL 编码嵌套的分享链接从 App 分享出来的链接经常是xxx://webview?urlhttps%3a%2f%2f...真正的目标 URL 藏在url参数里而且是编码过的。这种要先解码再提取。用urllib.parse的parse_qs和unquote组合from urllib.parse import urlparse, parse_qs, unquote def extract_real_url(raw: str) - str: 从带 url 参数的分享链接里抠出真实 URL p urlparse(raw) # 只看 query 部分找 url 参数 qs parse_qs(p.query) if url in qs: inner qs[url][0] # parse_qs 已经做过一次解码这里再兜底一次 return unquote(inner) return raw # 不是嵌套链接原样返回 # 测试 test snssdk1128://webview?urlhttps%3a%2f%2faweme.snssdk.com%2ffalcon%2fdouyi print(extract_real_url(test)) # 输出: https://aweme.snssdk.com/falcon/douyi逻辑说明parse_qs会把 query 拆成字典并自动做一次百分号解码所以url的值拿到时已经是https://...了。unquote是二次兜底防止有些链接编码了两层。参数说明parse_qs默认丢弃空值如果url后面为空会拿不到需要加keep_blank_valuesTrue。注意这种嵌套链接的 scheme 是自定义的snssdk1128://urlparse照样能拆不用特殊处理。注意不是所有分享链接都用url这个参数名有的用target、link。实际处理时先把参数名收集全再统一提取别只认一个。3.3 大文件流式处理与内存控制十万条以内上面的脚本直接读进内存没问题。上到百万级kept列表和seen集合会吃掉几百 MB。这时候改成流式写边读边写seen集合保留去重必须记但结果不攒在内存里。def process_stream(infile: str, outfile: str, tlds(.com,)): seen set() count 0 with open(infile, encodingutf-8) as fin, \ open(outfile, w, encodingutf-8) as fout: for line in fin: u normalize(line) if not u or not keep_by_tld(u, tlds) or u in seen: continue seen.add(u) fout.write(u \n) count 1 print(f保留 {count} 条)逻辑说明用with同时打开两个文件读一行处理一行写一行内存占用只跟seen集合大小相关。参数说明如果连seen都放不下千万级以上就得换布隆过滤器或先外部排序再去重那是另一个量级的方案日常业务用不到。4. 去重规则没定好等于白干4.1 什么算「重复」三种判定口径去重最容易被忽略的是判定标准。同样两条 URL按不同口径结论完全不同口径判定依据http://a.com/xvshttps://a.com/xa.com/xvsa.com/x/严格字符串完全一致不重复不重复忽略协议scheme 不参与重复不重复规范化后统一 schemehostpath重复重复我一般用第三种也就是前面normalize的做法。因为业务上要的是「同一个页面只留一条」协议和末尾斜杠的差异没有意义。但如果你的场景要区分 http 和 https比如做证书巡检就得用第一种别盲目规范化。4.2 query 参数要不要参与去重https://a.com/list?page1和?page2算不算重复取决于你要什么。做站点清单它们是同一个页面算重复做爬虫 URL 队列它们是不同内容不能去重。我的习惯是默认保留 query因为丢掉 query 可能把不同内容合并成一条属于不可逆的信息损失。真要按页面去重再单独写一个「去掉 query 后去重」的开关。def dedup_ignore_query(urls): 按 hostpath 去重忽略 query seen set() result [] for u in urls: p urlparse(u) key (p.hostname, p.path.rstrip(/)) if key in seen: continue seen.add(key) result.append(u) return result逻辑说明用(host, path)元组做 keyquery 不参与这样?page1和?page2只留第一条。参数说明path.rstrip(/)保证/x和/x/归一。这个函数适合「站点页面盘点」不适合「内容抓取」。4.3 用 SQL 做去重时的写法差异数据量再大或者已经在数据库里用 SQL 去重更省事。MySQL 和 SQL Server 的写法有区别别混用。MySQL 常用GROUP BY或ROW_NUMBER()-- MySQL 8.0按 url 去重保留 id 最小的一条 DELETE t1 FROM urls t1 JOIN urls t2 ON t1.url t2.url AND t1.id t2.id; -- 或者查询去重结果 SELECT url, MIN(id) AS id FROM urls GROUP BY url;SQL Server 用窗口函数更直观-- SQL ServerROW_NUMBER 标记重复删掉序号大于 1 的 WITH ranked AS ( SELECT id, url, ROW_NUMBER() OVER (PARTITION BY url ORDER BY id) AS rn FROM urls ) DELETE FROM ranked WHERE rn 1;逻辑说明MySQL 的自连接删除靠t1.id t2.id保留最小 idSQL Server 的ROW_NUMBER按 url 分组编号rn 1的就是重复。参数说明PARTITION BY url里的 url 字段如果是长文本建议先建索引或加哈希列否则大表上窗口函数会慢得离谱。这也是「sql 语句去重」类需求最常见的性能坑。5. 批量处理里最容易翻车的几个地方5.1 编码问题GBK 文件读成乱码现象脚本跑完输出一堆\ufffd或者直接抛UnicodeDecodeError。原因输入文件是 Windows 下用记事本存的 GBK 编码脚本按 UTF-8 读。解决先探测编码再读或者显式指定。# 方案一显式指定 open(urls.txt, encodinggbk) # 方案二探测后读需要 chardet import chardet with open(urls.txt, rb) as f: enc chardet.detect(f.read(10000))[encoding] with open(urls.txt, encodingenc or utf-8) as f: ...血泪经验别信「文件都是 UTF-8」这个假设尤其是别人发来的 txt。5.2 后缀匹配误伤.com匹配到了.community现象筛.com时把example.community也带进来了。原因用了host.endswith(com)而不是endswith(.com)少了那个点。解决后缀规则统一带前导点或者匹配时补上。# 错误 host.endswith(com) # example.community 也命中 # 正确 host.endswith(.com) # 只命中真正的 .com5.3 空行和注释行没过滤现象输出文件里混进空行、#开头的注释后续导入数据库报错。原因读取时没做行级清洗。解决normalize里先strip()空串直接返回注释行按业务决定是否跳过。5.4 去重后顺序乱了现象去重结果顺序和输入不一致排查时对不上。原因用了set存结果集合无序。解决用set只做判重结果单独用list按输入顺序追加就像 3.1 里的kept列表。5.5 大文件一次性 readlines 撑爆内存现象百万行文件跑脚本时内存飙到几个 G 然后被系统杀掉。原因用了f.readlines()把整个文件读进列表。解决改成for line in f逐行迭代Python 的文件对象本身就是迭代器不会一次性加载。6. 把筛选规则做成可配置的进阶玩法脚本能跑之后真正提升效率的是把规则从代码里抽出来。每次改后缀都要动代码、重跑太笨。我一般会配一个规则文件比如rules.json{ tlds: [.com, .cn, .org], path_exts: [.html, .php], ignore_query: false, dedup_mode: normalized }然后脚本读这个配置来决定行为import json def load_rules(pathrules.json): with open(path, encodingutf-8) as f: return json.load(f) def match(url: str, rules: dict) - bool: p urlparse(url) host p.hostname or # TLD 命中 或 路径后缀命中任一满足即可 tld_ok host.endswith(tuple(rules[tlds])) ext p.path.rsplit(., 1)[-1].lower() if . in p.path else ext_ok (. ext) in rules[path_exts] return tld_ok or ext_ok逻辑说明match把「域名后缀」和「路径后缀」两种筛选合并任一命中就保留适合「既要 .cn 站点、又要所有 .html 页面」的混合需求。参数说明rules[tlds]必须是带点的列表path_exts同理dedup_mode可以扩展成strict/normalized/ignore_query三档对应第 4 章讲的三种口径。验证方法很简单准备一个 20 行的小样本每行覆盖一种边界情况带编码、带端口、裸域名、末尾斜杠、大小写混合跑一遍看输出是否符合预期。这个样本文件我建议长期留着每次改规则先跑它比拿几万条真实数据试错快得多。一个具体技巧处理前先统计一遍输入里出现的所有 TLD 和路径后缀用Counter排序你会对数据长什么样心里有数规则也能定得更准。from collections import Counter from urllib.parse import urlparse def survey(infile): tlds, exts Counter(), Counter() with open(infile, encodingutf-8) as f: for line in f: p urlparse(line.strip()) if p.hostname: tlds[p.hostname.split(.)[-1]] 1 if . in p.path: exts[p.path.rsplit(., 1)[-1]] 1 print(TLD 分布:, tlds.most_common(10)) print(后缀分布:, exts.most_common(10))这套东西我从最早的手工 Excel 替换到写脚本再到规则外置前后改了三版。最大的教训是别急着写筛选逻辑先花十分钟看看数据长什么样。TLD 分布、编码情况、有没有嵌套链接这三样摸清了脚本一次就能写对省下的返工时间远超那十分钟。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑