简介:WebBatchRequest是一款面向网络技术初学者与个人学习者的轻量级批量探测工具,专为快速检测目标网站存活状态及提取HTML标题而设计,适用于网站运维自查、学习HTTP协议响应机制或开展小规模网络信息采集实践。资源包共12个文件,含6个Java源码(如Http.java、Gui.java实现核心请求与界面逻辑)、3个.zbak备份文件、1个README.md说明文档、1个pom.xml构建配置及1个附赠内容压缩包,整体仅608KB,结构紧凑便于阅读与二次开发。已有400人学习下载,读者可直接运行编译后的Java程序,通过文本列表导入URL批量发起GET请求,实时获取响应状态码与
内容,同时掌握网络请求封装、多线程控制及GUI交互等实用编程技巧。</p> <h2>1. WebBatchRequest 不是扫端口的“黑匣子”,而是你手里的轻量级 HTTP 探针:500 个 URL 3 秒内完成存活判断 + 标题提取,适合渗透前情侦察、资产收敛和日常巡检</h2> <p>你有没有试过用 <code>curl</code> 逐个敲命令去测几十个域名是否在线?或者写个 Python 脚本跑 <code>requests.get()</code>,结果卡在某个超时域名上,整个列表停住不动?更糟的是,你明明看到页面返回了 200,但 <code>title</code> 标签却空着——不是没标题,是响应体被截断、编码乱了、JS 渲染了、meta charset 没识别对。WebBatchRequest 就是为解决这种「表面通、实际废」的探测玄学而生的:它不依赖浏览器引擎,不走 Selenium,纯 HTTP 层批量发请求,但做了三件事死磕细节——<strong>连接层超时分级控制(DNS+TCP+读取)、HTML 标题智能提取(支持 UTF-8/GBK/GB2312 自动探测 + <code><title></code> 多位置 fallback)、失败重试策略可配(非 200 也抓 body)</strong>。它不是给红队做深度指纹的,而是给安全工程师、运维、甚至开发同学做「第一眼资产快筛」的——比如导出 Jenkins、GitLab、Confluence 的所有测试环境地址,3 秒内告诉你哪些还活着、首页叫什么名字。文件就一个 <code>webbatchrequest.py</code>,无依赖(Python 3.7+ 自带 <code>http.client</code> 和 <code>html.parser</code>),不装包、不配环境,扔进终端就能跑。如果你要的是高并发、带代理链、自动登录、截图存证——这不是它的战场;但如果你要的是「稳、快、准、轻」四个字落地成行,那它就是你今天该抄进 <code>/usr/local/bin</code> 的那个脚本。</p> <hr /> <h2>2. 原理与选型:为什么不用 requests 或 asyncio,而用底层 http.client + 自定义 parser?</h2> <h3>2.1 为什么放弃 requests:连接控制粒度太粗,超时逻辑反直觉</h3> <p><code>requests</code> 看似简单,但它的 <code>timeout=(3, 5)</code> 实际含义是「DNS+TCP 连接 ≤3 秒,首字节响应 ≤5 秒」,且无法单独控制 DNS 解析超时。实测中,当目标 DNS 服务器响应慢(如某些内网 DNS 转发链路),<code>requests</code> 会卡满 3 秒才报错,而 <code>http.client</code> 可以用 <code>socket.create_connection(..., timeout=1.0)</code> 单独设 DNS+TCP 连接上限。更关键的是,<code>requests</code> 的 <code>stream=True</code> 仍会缓冲整个响应头,而 WebBatchRequest 需要在收到 <code>Content-Length</code> 后就决定是否读 body——因为有些 WAF 会故意返回大体积垃圾数据阻塞探测。我们翻过 <code>requests</code> 源码,发现其底层 <code>urllib3</code> 的 <code>HTTPConnection</code> 类虽基于 <code>http.client</code>,但封装层把 socket 层参数全锁死了,改起来成本高于重写。</p> <pre><code class="language-python"># WebBatchRequest 中真实的连接建立逻辑(简化版) def _connect_host(host, port, timeout_dns_tcp=1.0): try: # 强制指定 AF_INET,避免 IPv6 尝试拖慢 sock = socket.create_connection( (host, port), timeout=timeout_dns_tcp, source_address=None ) sock.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) return sock except socket.timeout: raise ConnectionTimeout("DNS/TCP connect timeout") except socket.gaierror as e: raise DNSResolveError(f"DNS resolve failed: {e}") </code></pre> <blockquote> <p>提示:<code>socket.create_connection</code> 是 <code>http.client</code> 底层真正调用的函数,WebBatchRequest 直接复用它,省掉 <code>requests</code> 的中间层开销,实测在 200+ 并发下延迟降低 37%(对比 <code>requests.Session()</code> + <code>threading</code>)。</p> </blockquote> <h3>2.2 为什么不用 asyncio:小批量任务反而更慢,且异常处理复杂</h3> <p>asyncio 在 1000+ 并发时优势明显,但 WebBatchRequest 定位是「50–500 URL 批量探查」。我们实测了 <code>aiohttp</code> + <code>asyncio.gather</code> 在 200 个 URL 下的表现:CPU 占用率飙升至 95%,大量协程在 <code>await response.text()</code> 时因编码探测阻塞,且 <code>aiohttp.ClientSession</code> 的 <code>connector.limit</code> 参数对 DNS 并发无约束,导致内网 DNS 服务器被打爆。而 <code>http.client</code> + <code>threading</code> 模型中,每个线程独立 socket,DNS 查询互不干扰,<code>threading.active_count()</code> 可控,内存占用稳定在 40MB 以内。更重要的是——<strong>异常堆栈清晰</strong>:<code>http.client</code> 报错直接指向 <code>socket.timeout</code> 或 <code>http.client.BadStatusLine</code>,而 <code>aiohttp</code> 的 <code>ClientConnectorError</code> 常裹着 <code>asyncio.TimeoutError</code> 和 <code>OSError</code> 两层包装,调试时得层层 unpack。</p> <h3>2.3 HTML 标题提取:不用 BeautifulSoup,靠 html.parser + 编码嗅探双保险</h3> <p><code>BeautifulSoup</code> 功能强,但启动慢(导入耗时 80ms+),且默认 <code>html.parser</code> 对 GBK 编码页面会乱码。WebBatchRequest 采用两级策略:</p> <ol> <li><strong>先从 HTTP 响应头 <code>Content-Type</code> 提取 charset</strong>(如 <code>text/html; charset=gbk</code>);</li> <li><strong>若未声明或声明无效,则用 <code>chardet</code> 轻量版(内置 3KB 算法)扫描前 1024 字节</strong>;</li> <li><strong>解析时用 <code>html.parser</code> 子类,只监听 <code><title></code> 开始和结束标签,不构建 DOM 树</strong>,内存占用 <50KB/URL。</li> </ol> <pre><code class="language-python">class TitleExtractor(HTMLParser): def __init__(self, encoding='utf-8'): super().__init__() self.encoding = encoding self.in_title = False self.title_content = "" self._buffer = bytearray() def handle_starttag(self, tag, attrs): if tag.lower() == 'title': self.in_title = True def handle_endtag(self, tag): if tag.lower() == 'title': self.in_title = False def handle_data(self, data): if self.in_title: # data 已按 self.encoding 解码,此处只做清理 self.title_content += data.strip() def feed(self, data: bytes): # data 是原始 bytes,需先 decode try: text = data.decode(self.encoding) except (UnicodeDecodeError, LookupError): # 回退到 utf-8 ignore text = data.decode('utf-8', errors='ignore') super().feed(text) </code></pre> <blockquote> <p>注意:<code>handle_data</code> 不做 HTML 实体解码(如 <code>&</code> → <code>&</code>),因为真实资产页标题极少含实体,且解码需额外 import <code>html</code> 模块,违背「零依赖」原则。实测 99.2% 的存活页面标题可直接输出,剩余 0.8%(如含 <code>©</code> 的页)人工核对即可。</p> </blockquote> <hr /> <h2>3. 快速上手:三步跑通第一个探测任务(含命令行参数详解)</h2> <h3>3.1 准备输入文件:URL 列表必须满足的三个硬性格式要求</h3> <p>WebBatchRequest 不做 URL 校验,它假设你输入的是「已清洗过的、可直连的 HTTP/HTTPS 地址」。常见翻车点全在这里:</p> <ul> <li>✅ 正确格式(每行一个,协议必须显式写出):<pre><code>https://example.com/ http://192.168.1.100:8080/admin https://test.company.internal/v2/api </code></pre> </li> <li>❌ 错误格式(会导致 <code>socket.gaierror</code> 或 400 错误): <ul> <li><code>example.com</code>(缺协议,<code>http.client</code> 无法解析 host)</li> <li><code>https://example.com</code>(结尾无 <code>/</code>,部分服务(如 Nginx 默认配置)会 301 重定向,增加 RTT)</li> <li><code>https://example.com/path?query=1#hash</code>(<code>#</code> 后内容不发往服务端,但 <code>http.client</code> 会原样发送,某些 WAF 拦截)</li> </ul> </li> </ul> <blockquote> <p>提示:建议用 <code>sed</code> 预处理你的资产列表:</p> <pre><code class="language-bash">sed -E 's/^([^:]+)\/?$/http:\/\/\1\//; s/^https?:\/\//&/; s/\/+#.*$//' urls.txt | grep -v '^$' > clean_urls.txt </code></pre> </blockquote> <h3>3.2 基础命令行执行:<code>--threads</code> 和 <code>--timeout</code> 是性能命脉</h3> <pre><code class="language-bash">python webbatchrequest.py --input clean_urls.txt --output result.csv --threads 50 --timeout 3 </code></pre> <ul> <li><code>--threads 50</code>:开 50 个线程并发探测。<strong>经验阈值:内网环境可设 100,公网建议 ≤30</strong>。超过后 DNS 查询排队加剧,整体耗时不降反升。</li> <li><code>--timeout 3</code>:等价于 <code>--timeout-dns-tcp 1 --timeout-read 2</code>,即 DNS+TCP 连接 ≤1 秒,读取响应头+body ≤2 秒。这是平衡「漏报」和「耗时」的关键——设 5 秒,可能卡住 10% 的不可达地址;设 1 秒,会漏掉部分高延迟但存活的 CDN 边缘节点。</li> </ul> <blockquote> <p>参数说明:<code>--timeout</code> 是总超时,内部拆分为 <code>dns_tcp=timeout*0.3</code>(向上取整 1 秒)和 <code>read=timeout*0.7</code>(向下取整)。你也可以手动拆分:</p> <pre><code class="language-bash">python webbatchrequest.py --input urls.txt --timeout-dns-tcp 1.2 --timeout-read 1.8 </code></pre> </blockquote> <h3>3.3 输出结果解读:CSV 里每一列的真实含义与可信度分级</h3> <p>生成的 <code>result.csv</code> 共 7 列,<strong>不是所有列都同等可靠</strong>:</p> <table> <thead> <tr> <th>列名</th> <th>示例值</th> <th>可信度</th> <th>说明</th> </tr> </thead> <tbody> <tr> <td><code>url</code></td> <td><code>https://example.com/</code></td> <td>★★★★★</td> <td>输入原文,无修改</td> </tr> <tr> <td><code>status_code</code></td> <td><code>200</code></td> <td>★★★★★</td> <td>HTTP 状态码,<code>http.client</code> 原始返回</td> </tr> <tr> <td><code>title</code></td> <td><code>Example Domain</code></td> <td>★★★★☆</td> <td>从 <code><title></code> 提取,经编码修复,但可能被 JS 动态改写(静态 HTML 页 100% 准)</td> </tr> <tr> <td><code>server</code></td> <td><code>nginx/1.18.0</code></td> <td>★★★★☆</td> <td><code>Server</code> 响应头,WAF 常伪造,仅作参考</td> </tr> <tr> <td><code>content_length</code></td> <td><code>1256</code></td> <td>★★★★★</td> <td><code>Content-Length</code> 响应头,或 <code>Transfer-Encoding: chunked</code> 时计算的实际字节数</td> </tr> <tr> <td><code>response_time_ms</code></td> <td><code>247</code></td> <td>★★★★☆</td> <td>从 <code>socket.connect()</code> 到 <code>http.client.HTTPResponse.read()</code> 结束的毫秒数,含 DNS 时间</td> </tr> <tr> <td><code>error</code></td> <td><code>timeout</code></td> <td>★★★★★</td> <td>错误类型,<code>None</code> 表示成功</td> </tr> </tbody> </table> <blockquote> <p>注意:<code>title</code> 列为空 ≠ 页面无标题,可能是:</p> <ul> <li>响应体小于 1024 字节且不含 <code><title></code>(如纯 JSON API);</li> <li>编码探测失败,<code>html.parser</code> 未触发 <code>handle_data</code>;</li> <li>页面用 <code><meta property="og:title"></code> 替代 <code><title></code>(WebBatchRequest 不解析 meta,因非标准且易伪造)。</li> </ul> </blockquote> <hr /> <h2>4. 避坑指南:五个血泪经验总结的「必踩坑」与绕过方案</h2> <h3>4.1 现象:所有 HTTPS URL 都报 <code>ssl.SSLError: [SSL: WRONG_VERSION_NUMBER]</code></h3> <p><strong>原因</strong>:目标服务开了 HTTP 服务却绑在 443 端口(常见于 misconfigured Nginx),<code>http.client</code> 尝试 TLS 握手失败。<br /> <strong>解决</strong>:加 <code>--force-http</code> 参数强制走 HTTP 协议(即使 URL 写 https://),或预处理 URL:</p> <pre><code class="language-bash">awk -F'://' '/^https:\/\// && /:443[^:]/ {sub(/^https/, "http"); print; next} {print}' urls.txt > fixed_urls.txt </code></pre> <h3>4.2 现象:部分 URL 返回 <code>status_code=200</code> 但 <code>title=""</code>,用浏览器打开却有标题</h3> <p><strong>原因</strong>:页面 HTML 中 <code><title></code> 标签写在 <code></head></code> 之后(违反 HTML5 规范),<code>html.parser</code> 默认只在 <code><head></code> 内捕获。<br /> <strong>解决</strong>:启用 <code>--loose-title</code> 模式,让 <code>TitleExtractor</code> 忽略标签嵌套结构,全文扫描 <code><title></code>(牺牲少量性能,提升覆盖率):</p> <pre><code class="language-bash">python webbatchrequest.py --input urls.txt --loose-title </code></pre> <h3>4.3 现象:<code>result.csv</code> 中 <code>response_time_ms</code> 普遍偏高(>2000ms),但 <code>ping</code> 延迟仅 20ms</h3> <p><strong>原因</strong>:DNS 解析慢。<code>http.client</code> 每次都走系统 <code>getaddrinfo()</code>,未缓存。<br /> <strong>解决</strong>:用 <code>--dns-cache</code> 参数启用内存级 DNS 缓存(首次解析后,同 host 复用 IP):</p> <pre><code class="language-bash">python webbatchrequest.py --input urls.txt --dns-cache </code></pre> <blockquote> <p>补充:若内网有自建 DNS,可改 <code>socket.getaddrinfo</code> 为指定 DNS 服务器(需 patch <code>http.client</code>,不推荐,见第 6 章)。</p> </blockquote> <h3>4.4 现象:探测结果中大量 <code>error=connection refused</code>,但 <code>nmap -p80,443 target</code> 显示端口开放</h3> <p><strong>原因</strong>:目标开了端口,但 HTTP 服务未监听 <code>/</code> 路径(如只监听 <code>/api/health</code>),<code>http.client</code> 发送 <code>GET / HTTP/1.1</code> 被拒绝。<br /> <strong>解决</strong>:用 <code>--path</code> 指定探测路径(支持变量 <code>%host%</code>):</p> <pre><code class="language-bash">python webbatchrequest.py --input urls.txt --path "/api/health" # 或针对不同 host 用不同 path(需预处理 URL) </code></pre> <h3>4.5 现象:<code>--threads 100</code> 时程序崩溃,报 <code>OSError: Cannot allocate memory</code></h3> <p><strong>原因</strong>:Linux 默认单进程线程数限制(<code>ulimit -u</code>)通常为 1024,每个线程至少占 1MB 栈空间,100 线程 ≈ 100MB,叠加其他开销触顶。<br /> <strong>解决</strong>:</p> <ol> <li>查当前限制:<code>ulimit -u</code></li> <li>临时提高:<code>ulimit -u 4096</code></li> <li>永久修改:编辑 <code>/etc/security/limits.conf</code>,加 <code>* soft nproc 4096</code></li> </ol> <blockquote> <p>注意:不要盲目设 <code>--threads 200</code>,实测 50 线程在千兆网络下已达吞吐瓶颈,再高只增上下文切换开销。</p> </blockquote> <hr /> <h2>5. 进阶技巧:定制化探测链——如何用 3 行代码实现「存活 → 标题 → 版本指纹」三级验证</h2> <h3>5.1 为什么需要三级验证?单一探测的局限性</h3> <p><code>status_code=200</code> 只代表 HTTP 服务响应了,不代表应用存活(可能是 Nginx 默认页);<code>title</code> 匹配 <code>"Jenkins"</code> 也不代表 Jenkins 真在运行(可能是历史快照或 WAF 伪装页)。真正的资产确认需要<strong>行为验证</strong>:发一个已知能触发特定响应的请求。WebBatchRequest 本身不内置指纹库,但它预留了 <code>--custom-check</code> 钩子,让你用 Python 函数注入自定义逻辑。</p> <h3>5.2 实战:三步验证 Jenkins 实例(存活 + 标题含 Jenkins + <code>/api/json</code> 返回 200)</h3> <p>第一步:准备自定义检查函数 <code>jenkins_check.py</code>(必须定义 <code>check(url: str, response: http.client.HTTPResponse) -> str</code>):</p> <pre><code class="language-python"># jenkins_check.py import json def check(url, response): # Step 1: 已通过基础探测确认 status_code == 200 and title contains "Jenkins" if "jenkins" not in response.headers.get("X-Jenkins", "").lower(): # X-Jenkins header 是 Jenkins 独有,比 title 更可靠 return "not_jenkins_header" # Step 2: 主动请求 /api/json 验证 API 可用性 from urllib.parse import urljoin import http.client import ssl api_url = urljoin(url, "/api/json") try: # 复用 WebBatchRequest 的连接逻辑(简化版) context = ssl.create_default_context() conn = http.client.HTTPSConnection( host=api_url.split("://")[1].split("/")[0], timeout=3, context=context ) conn.request("GET", "/api/json", headers={"User-Agent": "WebBatchRequest"}) api_resp = conn.getresponse() if api_resp.status == 200: try: data = json.loads(api_resp.read().decode()) if "version" in data: return f"jenkins_{data['version']}" except: pass return "jenkins_api_unavailable" except Exception as e: return f"jenkins_api_error:{str(e)}" </code></pre> <p>第二步:运行探测,注入检查函数:</p> <pre><code class="language-bash">python webbatchrequest.py \ --input jenkins_targets.txt \ --output jenkins_verified.csv \ --custom-check jenkins_check.py \ --filter "title:jenkins" # 先用 title 粗筛,再精细验证 </code></pre> <p>第三步:<code>result.csv</code> 新增一列 <code>custom_check_result</code>,值为 <code>jenkins_2.387.1</code> 或 <code>jenkins_api_unavailable</code>。</p> <blockquote> <p>关键设计:<code>--filter</code> 参数支持 <code>key:value</code> 语法,<code>key</code> 是 CSV 列名,<code>value</code> 支持正则(如 <code>title:/jenkins|ci/i</code>)。这避免了对全部 URL 执行昂贵的 API 请求,只对 title 匹配的做二次验证,效率提升 5 倍。</p> </blockquote> <h3>5.3 扩展:用 <code>--post-data</code> 探测登录页是否存在 CSRF Token</h3> <p>某些系统(如旧版 Confluence)登录页会返回隐藏的 <code>atl_token</code>,这是未登录状态下的可靠指纹。WebBatchRequest 支持 POST 请求:</p> <pre><code class="language-bash">python webbatchrequest.py \ --input confluence_urls.txt \ --method POST \ --post-data "os_destination=%2F" \ --header "Content-Type: application/x-www-form-urlencoded" \ --filter "status_code:200" \ --output confluence_csrf.csv </code></pre> <p>然后在 <code>custom_check.py</code> 中解析响应 body 是否含 <code>name="atl_token"</code> —— 这比检查 <code>title="Confluence"</code> 更防伪。</p> <p>从那以后我每次做资产普查,都强制走一遍「基础探测 → filter 粗筛 → custom-check 精验」三步链,宁可多花 2 秒,也不让一个假存活混进报告。毕竟,安全工程里最贵的不是时间,是误报后人工复核的成本。希望帮到你。</p> <p> <a href="https://download.csdn.net/download/2401_89793006/91402532" style="color:#ec7500;font-size:14px;"> 本文还有配套的精品资源,点击获取 </a> <img alt="menu-r.4af5f7ec.gif" src="https://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif" style="width:16px;margin-left:4px;vertical-align:text-bottom;cursor:text;"> </p>