爬虫技术分享

网络爬虫技术分享

作者:技术分享
日期:2026年3月
适用语言:Python / Java

一、什么是网络爬虫?

网络爬虫(Web Crawler),又称网络蜘蛛或网络机器人,是一种按照一定规则自动抓取网页信息的自动化程序。其本质是模拟人类浏览器访问网页的行为,通过发送 HTTP 请求获取页面内容,再从中提取有价值的结构化数据并加以存储,最终服务于数据分析、业务监控、信息聚合等场景。

爬虫的工作流程可以简单概括为四个核心步骤:

获取网页 → 提取信息 → 保存数据 → 自动化调度 

这四个步骤形成了一个完整的数据采集闭环。理解这个闭环,是学习爬虫技术的基础。

为什么要学爬虫?
在数据驱动的时代,数据就是生产力。无论是金融分析、市场调研还是风控建模,高质量、及时准确的数据都是前提。爬虫技术提供了一种低成本、高效率的数据获取手段,在银行、证券、保险等金融机构中有着广泛的实际应用价值。

二、爬虫在银行相关技术中的应用

爬虫技术在银行及金融领域的应用远比想象中广泛,以下是几个典型场景:

1. 舆情监控与品牌风控

银行需要实时关注网络上与自身品牌、产品相关的舆论动态。通过爬虫自动采集新闻门户、社交媒体、论坛等渠道的信息,可以及时发现负面舆情,为公关和风控部门提供预警。

2. 金融数据采集

对公开的利率信息、汇率数据、债券发行公告、同业产品定价等进行定时采集和结构化存储,为量化分析和市场研究提供数据基础。

3. 信用信息辅助核查

在贷前审核环节,爬虫可以辅助采集企业工商信息、司法裁判文书、行政处罚记录等公开数据,补充征信报告的覆盖面,提升风险评估的全面性。

4. 监管政策跟踪

自动抓取银保监会、央行等监管机构网站的政策法规和公告通知,第一时间推送给合规团队,确保政策响应的时效性。

5. 竞品分析与产品研究

定期采集同业银行的产品信息(如理财产品收益率、信用卡权益等),形成对比分析报告,为产品设计和定价策略提供参考。

注意: 银行场景下的爬虫开发需格外注意合规性,必须遵守 robots.txt 协议,不采集非公开数据,避免对目标网站造成过大访问压力,并确保数据使用符合隐私保护相关法规。

三、获取静态网页

3.1 什么是静态网页?

静态网页是指服务器直接将完整的 HTML 文档返回给客户端的页面,所有数据都嵌入在 HTML 响应体中,无需额外的 JavaScript 渲染。这是最简单的爬取场景,也是学习爬虫的起点。

对于静态页面,我们只需要发送一个普通的 HTTP GET 请求,就能获取到包含所有目标数据的 HTML 文本。

3.2 Python 实现

Python 的 requests 库是目前最流行的 HTTP 客户端库之一,语法简洁直观,非常适合快速开发爬虫原型。

import requests url ="https://cc.cmbchina.com/notice/1/" headers ={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",# 模拟浏览器} response = requests.get(url, headers=headers)if response.status_code ==200: html = response.text # 获取 HTML 内容print(html)else:print("请求失败,状态码:", response.status_code)

这里有一个关键细节:为什么要设置 User-Agent 很多网站会识别请求头中的 User-Agent 字段,如果发现是非浏览器的请求(例如 Python 的默认 python-requests/x.x.x),可能会直接拒绝访问或返回反爬验证页面。通过伪装成浏览器的 User-Agent,可以绕过这类基础的反爬措施。

3.3 Java 实现

在 Java 生态中,Apache HttpClient 是使用最广泛的 HTTP 客户端库,提供了完善的连接池管理、重试机制和 HTTPS 支持,更适合生产级别的爬虫项目。

importorg.apache.http.HttpEntity;importorg.apache.http.client.methods.CloseableHttpResponse;importorg.apache.http.client.methods.HttpGet;importorg.apache.http.impl.client.CloseableHttpClient;importorg.apache.http.impl.client.HttpClients;importorg.apache.http.util.EntityUtils;publicclassSpider{publicstaticvoidmain(String[] args){try(CloseableHttpClient httpClient =HttpClients.createDefault()){// 1. 创建 GET 请求HttpGet request =newHttpGet("https://ssr1.scrape.center/");// 2. 设置请求头 request.setHeader("User-Agent","Mozilla/5.0");// 3. 发送请求并获取响应try(CloseableHttpResponse response = httpClient.execute(request)){// 4. 获取响应实体(网页内容)HttpEntity entity = response.getEntity();if(entity !=null){String html =EntityUtils.toString(entity);System.out.println(html);}}}catch(Exception e){ e.printStackTrace();}}}
小贴士: 上面的 Java 代码使用了 try-with-resources 语法,确保 HTTP 连接在使用完毕后自动关闭,避免连接泄漏——这在长时间运行的爬虫任务中尤为重要。

四、提取信息

拿到 HTML 文本之后,我们需要从中提取出目标数据。这是爬虫的核心环节,也是技巧最多的部分。下面介绍几种主流方案,并对比它们各自的优缺点。

4.1 正则表达式

正则表达式是最原始的解析方式,直接对 HTML 字符串进行模式匹配,无需任何额外依赖。

Python 示例:提取电影标题

import re import requests url ="https://ssr1.scrape.center/" headers ={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36",} response = requests.get(url, headers=headers)if response.status_code ==200: html = response.text # 使用正则表达式提取电影标题 pattern = re.compile(r'<h2[^>]*class=[\'"]m-b-sm[\'"][^>]*>\s*(.*?)\s*</h2>', re.DOTALL) movies = pattern.findall(html)for i, movie inenumerate(movies,1):print(f"{i}. {movie.strip()}")else:print("请求失败,状态码:", response.status_code)

正则含义解析:

模式片段含义
<h2[^>]*匹配 h2 标签的开始部分(含任意属性)
class=['"]m-b-sm['"]精确定位 class 为 m-b-sm 的 h2 元素
>\s*(.*?)\s*</h2>提取标签内的文本内容,忽略首尾空白
(.*?)非贪婪捕获组,即我们想要的电影名

Java 示例:

Pattern pattern =Pattern.compile("<h2 data-v-7f856186=\"\" class=\"m-b-sm\">(.+?)</h2>");Matcher matcher = pattern.matcher(html);int count =0;while(matcher.find()){ count++;String fullName = matcher.group(1).trim();System.out.println(count +". "+ fullName);}

正则表达式的局限性: HTML 结构复杂且多变,正则表达式难以处理嵌套标签、属性顺序变化、换行等情况,一旦页面结构调整,正则极易失效,维护成本极高。实际项目中不推荐作为主要解析手段。


4.2 Python 主流解析库对比

为了解决正则表达式的痛点,Python 生态提供了多种专业的 HTML 解析库。

4.2.1 XPath(lxml)

XPath 是 W3C 标准的 XML/HTML 路径语言,基于文档树结构进行节点定位,语义清晰,性能优秀。

from lxml import etree tree = etree.HTML(html) movies = tree.xpath('//h2[@class="m-b-sm"]/text()')

XPath 语法说明:

  • //h2:查找文档中所有 <h2> 标签
  • [@class="m-b-sm"]:筛选 class 属性为 m-b-sm 的节点
  • /text():提取该节点的文本内容
4.2.2 BeautifulSoup

BeautifulSoup 将 HTML 解析为 DOM 树,提供了直观的 Python API,对新手非常友好,容错性强(能处理不规范的 HTML)。

from bs4 import BeautifulSoup soup = BeautifulSoup(html,"lxml")# 推荐使用 lxml 解析器,速度更快 movies = soup.find_all("h2", class_="m-b-sm")for movie in movies:print(movie.get_text())
4.2.3 pyquery

pyquery 借鉴了前端 jQuery 的 CSS 选择器语法,对于熟悉前端开发的工程师来说上手极快。

from pyquery import PyQuery as pq doc = pq(html) movies =[item.text()for item in doc('h2.m-b-sm').items()]
4.2.4 parsel(推荐)

parsel 是 Scrapy 框架内置的解析库,同时支持 XPath、CSS 选择器和正则表达式,功能最为全面,也是目前生产项目中使用最广泛的方案。

from parsel import Selector selector = Selector(text=html)# 使用 CSS 选择器 movies = selector.css("h2.m-b-sm::text").getall()# 使用 XPath movies = selector.xpath('//h2[@class="m-b-sm"]/text()').getall() movies =[movie.strip()for movie in movies]

各解析方案横向对比:

解析方式语法风格容错性性能推荐场景
正则表达式模式匹配简单字符串提取
XPath (lxml)路径语言一般复杂层级结构
BeautifulSoupPython API快速原型、脏数据
pyqueryCSS 选择器一般前端熟悉的开发者
parselXPath + CSS一般生产项目(推荐)

4.3 Java:使用 Jsoup

Java 生态中,Jsoup 是解析 HTML 的首选库,支持 CSS 选择器语法,API 设计简洁,同时内置了 HTTP 请求功能,可以一步完成获取和解析。

importorg.jsoup.Jsoup;importorg.jsoup.nodes.Document;importorg.jsoup.nodes.Element;importorg.jsoup.select.Elements;publicclassHttpClientCrawler{publicstaticvoidmain(String[] args)throwsException{String url ="https://ssr1.scrape.center/";// 1. 获取并解析 HTML 文档(Jsoup 内置 HTTP 请求)Document doc =Jsoup.connect(url).userAgent("Mozilla/5.0").get();// 2. 使用 CSS 选择器定位元素Elements movieElements = doc.select("div.el-card.item h2.m-b-sm");int count =0;for(Element element : movieElements){String fullName = element.text().trim();// 拆分中英文名String[] nameParts = fullName.split(" - ",2);String chineseName = nameParts[0];String englishName =(nameParts.length >1)? nameParts[1]:"无英文名";System.out.printf("%d. %s - %s%n",++count, chineseName, englishName);}}}

五、获取动态页面

随着前端技术的发展,越来越多的页面采用了 JavaScript 动态渲染技术(如 Vue.js、React 等),数据不再直接包含在 HTML 源码中,而是由浏览器在加载后通过 JavaScript 动态生成。这类页面需要特殊处理。

5.1 方案一:Ajax 接口爬取

原理: 很多"动态页面"本质上是通过 Ajax 请求从后端 API 获取 JSON 数据,再由前端渲染成页面。我们只需找到这个 API 接口,直接请求它,就能绕过前端渲染,直接拿到干净的结构化数据。

如何找到 Ajax 接口?

打开浏览器开发者工具(F12),切换到「网络」标签页,筛选 XHRFetch 类型的请求,刷新页面后即可看到所有的 Ajax 请求,找到返回目标数据的接口 URL 即可。

Python 示例:

import requests url ="https://cc.cmbchina.com/api/content/list-paged" headers ={"Accept":"application/json","Content-Type":"application/json;charset=UTF-8","Origin":"https://cc.cmbchina.com","Referer":"https://cc.cmbchina.com/notice/1/","User-Agent":"Mozilla/5.0"}for page inrange(1,3): payload ={"url":"cusservice/news/","type":"notice","pageIndex": page,"pageSize":15} resp = requests.post(url, headers=headers, json=payload, timeout=15) data = resp.json()print(f"\n===== 第 {page} 页 =====")for item in data["body"]["pageList"]:print(item["title"],"|", item["timeEffective"])

Java 示例(使用 Java 11 内置 HttpClient + Jackson):

importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.nio.charset.StandardCharsets;importjava.time.Duration;publicclassHttpClientCrawler{privatestaticfinalStringAPI_URL="https://spa1.scrape.center/api/movie/?limit=10&offset=0";publicstaticvoidmain(String[] args)throwsException{HttpClient client =HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();HttpRequest request =HttpRequest.newBuilder().uri(URI.create(API_URL)).header("User-Agent","Mozilla/5.0").header("Accept","application/json").GET().build();HttpResponse<String> response = client.send( request,HttpResponse.BodyHandlers.ofString(StandardCharsets.UTF_8));if(response.statusCode()!=200){System.out.println("请求失败,状态码:"+ response.statusCode());return;}ObjectMapper mapper =newObjectMapper();JsonNode rootNode = mapper.readTree(response.body());JsonNode results = rootNode.get("results");if(results !=null&& results.isArray()){for(JsonNode movie : results){int id = movie.has("id")? movie.get("id").asInt():0;String name = movie.has("name")? movie.get("name").asText():"";double score = movie.has("score")? movie.get("score").asDouble():0.0;System.out.println(id +". "+ name +" 评分:"+ score);}}}}
Ajax 爬取的优点: 无需渲染 HTML,数据直接是结构化的 JSON,解析简单,运行速度快,资源消耗少。这是处理动态页面的首选方案。

5.2 方案二:异步爬虫(高性能并发采集与存储)

在实际项目中,待采集的数据量往往很大(几十页甚至上百页),逐条同步请求效率极低。异步爬虫通过非阻塞 I/O + 协程/多线程并发,可以大幅提升采集效率。

5.2.1 异步爬虫的核心优势
  • 高吞吐量:利用异步 I/O,在等待网络响应的间隙发起其他请求,极大减少空闲等待时间
  • 资源利用率高:相比多线程/多进程方案,协程的内存开销更小,可以轻松支撑数百个并发连接
  • 可控的并发度:通过信号量(Semaphore)精确控制并发数量,避免对目标服务器造成过大压力
  • 与数据库异步集成:配合异步数据库驱动(如 aiomysql),实现采集与存储的全链路异步化
5.2.2 Python 异步爬虫实现(aiohttp + aiomysql)

以下是一个完整的生产级异步爬虫示例,实现了分页列表采集 → 详情页正文抓取 → MySQL 异步入库的全流程:

import asyncio import aiomysql import aiohttp import time from bs4 import BeautifulSoup start_time = time.time() list_url ="https://cc.cmbchina.com/api/content/list-paged" detail_api_url ="https://cc.cmbchina.com/api/content/list/detail" detail_page_prefix ="https://cc.cmbchina.com/notice/" headers ={"Accept":"application/json","Content-Type":"application/json;charset=UTF-8","Origin":"https://cc.cmbchina.com","Referer":"https://cc.cmbchina.com/notice/1/","User-Agent":"Mozilla/5.0"} MYSQL_CONFIG ={"host":"127.0.0.1","port":3306,"user":"root","password":"123456","db":"zhaohang","charset":"utf8mb4"}# 并发控制:最多同时发起的详情请求数量 MAX_CONCURRENCY =5# ────────────────────────────────────────────────# 数据库相关# ────────────────────────────────────────────────asyncdefcreate_pool():"""创建 aiomysql 连接池"""returnawait aiomysql.create_pool( host=MYSQL_CONFIG["host"], port=MYSQL_CONFIG["port"], user=MYSQL_CONFIG["user"], password=MYSQL_CONFIG["password"], db=MYSQL_CONFIG["db"], charset=MYSQL_CONFIG["charset"], autocommit=False, minsize=1, maxsize=10)asyncdefcreate_table(pool):"""创建数据表(如不存在)""" create_sql =""" CREATE TABLE IF NOT EXISTS cmb_notice ( id BIGINT PRIMARY KEY AUTO_INCREMENT, page_index INT NOT NULL, notice_id BIGINT NULL, guid VARCHAR(64) NULL, name VARCHAR(64) NOT NULL, title VARCHAR(500) NOT NULL, time_effective DATETIME NULL, detail_page_url VARCHAR(255) NULL, content LONGTEXT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE KEY uk_name (name) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; """asyncwith pool.acquire()as conn:asyncwith conn.cursor()as cursor:await cursor.execute(create_sql)await conn.commit()asyncdefsave_to_mysql(pool, record):"""使用 UPSERT 语法写入数据库,避免重复插入""" insert_sql =""" INSERT INTO cmb_notice ( page_index, notice_id, guid, name, title, time_effective, detail_page_url, content ) VALUES (%s, %s, %s, %s, %s, %s, %s, %s) ON DUPLICATE KEY UPDATE page_index = VALUES(page_index), notice_id = VALUES(notice_id), guid = VALUES(guid), title = VALUES(title), time_effective = VALUES(time_effective), detail_page_url = VALUES(detail_page_url), content = VALUES(content), updated_at = CURRENT_TIMESTAMP """asyncwith pool.acquire()as conn:try:asyncwith conn.cursor()as cursor:await cursor.execute(insert_sql,( record["page_index"], record["notice_id"], record["guid"], record["name"], record["title"], record["time_effective"], record["detail_page_url"], record["content"]))await conn.commit()print(f"已写入数据库: {record['name']}")except Exception as e:await conn.rollback()print(f"写入数据库失败 {record['name']}: {e}")# ────────────────────────────────────────────────# 网络请求相关# ────────────────────────────────────────────────asyncdefget_notice_content(session: aiohttp.ClientSession, name:str)->str:"""异步获取单条公告的正文内容"""ifnot name:return"name 为空,无法获取正文" payload ={"name": name,"parent":"news","type":"notice"}try:asyncwith session.post( detail_api_url, headers=headers, json=payload, timeout=aiohttp.ClientTimeout(total=15))as resp: resp.raise_for_status() data =await resp.json(content_type=None)except Exception as e:returnf"详情请求异常: {e}"if data.get("returnCode")!="SUC0000":returnf"详情接口返回失败: {data}" html_content = data.get("body",{}).get("contentInfo","")ifnot html_content:return"未获取到正文内容" soup = BeautifulSoup(html_content,"html.parser") content_list =[ p.get_text(" ", strip=True).replace("\xa0"," ")for p in soup.find_all("p")if p.get_text(" ", strip=True).replace("\xa0"," ")]return"\n".join(content_list)if content_list else"正文解析后为空"asyncdeffetch_page_list(session: aiohttp.ClientSession, page:int):"""异步获取列表页数据""" payload ={"url":"cusservice/news/","type":"notice","pageIndex": page,"pageSize":15}try:asyncwith session.post( list_url, headers=headers, json=payload, timeout=aiohttp.ClientTimeout(total=15))as resp: resp.raise_for_status()returnawait resp.json(content_type=None)except Exception as e:print(f"第 {page} 页列表请求失败: {e}")returnNoneasyncdefprocess_item(session, pool, item, page, idx, semaphore):"""处理单条公告:获取正文并写入数据库""" title = item.get("title","") time_effective = item.get("timeEffective",None) name = item.get("name","") guid = item.get("guid","") notice_id = item.get("id",None) detail_page_url =f"{detail_page_prefix}{name}.htm"if name elseNoneprint(f"正在抓取:第 {page} 页 第 {idx} 条 -> {title}")asyncwith semaphore:# 信号量控制并发try: content =await get_notice_content(session, name)except Exception as e: content =f"抓取正文失败: {e}" record ={"page_index": page,"notice_id": notice_id,"guid": guid,"name": name,"title": title,"time_effective": time_effective,"detail_page_url": detail_page_url,"content": content }await save_to_mysql(pool, record)asyncdefprocess_page(session, pool, page, semaphore):"""处理单页:拉取列表,并发抓取所有条目""" data =await fetch_page_list(session, page)if data isNone:returnif data.get("returnCode")!="SUC0000":print(f"第 {page} 页列表接口失败: {data}")return page_list = data.get("body",{}).get("pageList",[])print(f"\n===== 正在抓取第 {page} 页,共 {len(page_list)} 条 =====") tasks =[ process_item(session, pool, item, page, idx, semaphore)for idx, item inenumerate(page_list,1)]await asyncio.gather(*tasks)print(f"第 {page} 页抓取完成")# ────────────────────────────────────────────────# 入口# ────────────────────────────────────────────────asyncdefcrawl_notice_pages(start_page:int, end_page:int): pool =await create_pool()await create_table(pool) semaphore = asyncio.Semaphore(MAX_CONCURRENCY) connector = aiohttp.TCPConnector(limit=20, ssl=False)asyncwith aiohttp.ClientSession(connector=connector)as session: page_tasks =[ process_page(session, pool, page, semaphore)for page inrange(start_page, end_page +1)]await asyncio.gather(*page_tasks) pool.close()await pool.wait_closed()if __name__ =="__main__": start_page =1 end_page =7 asyncio.run(crawl_notice_pages(start_page, end_page)) end_time = time.time()print("爬取时间为:", end_time - start_time)print("抓取完成并已写入 MySQL。")
5.2.3 Java 异步爬虫实现(HttpClient + CompletableFuture + HikariCP)

Java 11 内置的 HttpClient 天生支持异步请求(sendAsync),配合 CompletableFuture 可以实现类似 Python asyncio 的并发效果,同时使用 Semaphore 控制并发度、HikariCP 管理数据库连接池:

importcom.fasterxml.jackson.databind.JsonNode;importcom.fasterxml.jackson.databind.ObjectMapper;importcom.fasterxml.jackson.databind.node.ObjectNode;importcom.zaxxer.hikari.HikariConfig;importcom.zaxxer.hikari.HikariDataSource;importorg.jsoup.Jsoup;importorg.jsoup.nodes.Document;importorg.jsoup.nodes.Element;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.sql.Connection;importjava.sql.PreparedStatement;importjava.sql.SQLException;importjava.time.Duration;importjava.time.Instant;importjava.util.ArrayList;importjava.util.List;importjava.util.concurrent.*;/** * Maven 依赖: * com.fasterxml.jackson.core:jackson-databind:2.17.1 * com.zaxxer:HikariCP:5.1.0 * org.jsoup:jsoup:1.17.2 * com.mysql:mysql-connector-j:8.3.0 * * 要求:JDK 11+ */publicclassHttpClientCrawler{// ── URL 常量 ──privatestaticfinalStringLIST_URL="https://cc.cmbchina.com/api/content/list-paged";privatestaticfinalStringDETAIL_API_URL="https://cc.cmbchina.com/api/content/list/detail";privatestaticfinalStringDETAIL_PAGE_PREFIX="https://cc.cmbchina.com/notice/";// ── HTTP 请求头 ──privatestaticfinalStringH_ACCEPT="application/json";privatestaticfinalStringH_CONTENT_TYPE="application/json;charset=UTF-8";privatestaticfinalStringH_ORIGIN="https://cc.cmbchina.com";privatestaticfinalStringH_REFERER="https://cc.cmbchina.com/notice/1/";privatestaticfinalStringH_USER_AGENT="Mozilla/5.0";// ── MySQL 配置 ──privatestaticfinalStringDB_HOST="127.0.0.1";privatestaticfinalintDB_PORT=3306;privatestaticfinalStringDB_USER="root";privatestaticfinalStringDB_PASSWORD="123456";privatestaticfinalStringDB_NAME="zhaohang";// ── 并发参数 ──privatestaticfinalintMAX_CONCURRENCY=5;// ── SQL ──privatestaticfinalStringCREATE_TABLE_SQL="CREATE TABLE IF NOT EXISTS cmb_notice (...) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";privatestaticfinalStringUPSERT_SQL="INSERT INTO cmb_notice (...) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE ...";// ── 内部数据记录类 ──staticrecordNoticeRecord(int pageIndex,Long noticeId,String guid,String name,String title,String timeEffective,String detailPageUrl,String content ){}privatefinalHttpClient httpClient;privatefinalHikariDataSource dataSource;privatefinalObjectMapper objectMapper;privatefinalSemaphore semaphore;privatefinalExecutorService dbExecutor;publicHttpClientCrawler(){this.objectMapper =newObjectMapper();this.semaphore =newSemaphore(MAX_CONCURRENCY);this.dbExecutor =Executors.newFixedThreadPool(10);this.httpClient =HttpClient.newBuilder().version(HttpClient.Version.HTTP_1_1).connectTimeout(Duration.ofSeconds(15)).executor(Executors.newCachedThreadPool()).build();this.dataSource =buildDataSource();}privatestaticHikariDataSourcebuildDataSource(){HikariConfig config =newHikariConfig();String url ="jdbc:mysql://"+DB_HOST+":"+DB_PORT+"/"+DB_NAME+"?connectionCollation=utf8mb4_unicode_ci"+"&useSSL=false&allowPublicKeyRetrieval=true"+"&serverTimezone=Asia/Shanghai&autoReconnect=true"; config.setJdbcUrl(url); config.setUsername(DB_USER); config.setPassword(DB_PASSWORD); config.setAutoCommit(true); config.setMaximumPoolSize(10);returnnewHikariDataSource(config);}// ── 异步 HTTP POST ──privateCompletableFuture<String>postJson(String url,String jsonBody){HttpRequest request =HttpRequest.newBuilder().uri(URI.create(url)).timeout(Duration.ofSeconds(15)).header("Accept",H_ACCEPT).header("Content-Type",H_CONTENT_TYPE).header("Origin",H_ORIGIN).header("Referer",H_REFERER).header("User-Agent",H_USER_AGENT).POST(HttpRequest.BodyPublishers.ofString(jsonBody)).build();return httpClient.sendAsync(request,HttpResponse.BodyHandlers.ofString()).thenApply(HttpResponse::body);}// ── 异步获取正文(受信号量限速)──privateCompletableFuture<String>getNoticeContent(String name){if(name ==null|| name.isBlank())returnCompletableFuture.completedFuture("name 为空");ObjectNode payload = objectMapper.createObjectNode(); payload.put("name", name).put("parent","news").put("type","notice");returnCompletableFuture.supplyAsync(()->{try{ semaphore.acquire();returnnull;}catch(InterruptedException e){thrownewCompletionException(e);}}, dbExecutor).thenCompose(ignored ->postJson(DETAIL_API_URL, payload.toString()).thenApply(this::parseContent).exceptionally(e ->"详情请求异常: "+ e.getMessage()).whenComplete((r, e)-> semaphore.release()));}// ── 异步写入数据库 ──privateCompletableFuture<Void>saveToMysqlAsync(NoticeRecord record){returnCompletableFuture.runAsync(()->{try(Connection conn = dataSource.getConnection();PreparedStatement ps = conn.prepareStatement(UPSERT_SQL)){// 设置参数并执行... ps.executeUpdate();System.out.printf("已写入数据库: %s%n", record.name());}catch(SQLException e){System.err.printf("写入失败 %s: %s%n", record.name(), e.getMessage());}}, dbExecutor);}// ── 入口 ──publicvoidcrawl(int startPage,int endPage)throwsException{// 建表、逐页并发处理、等待完成、关闭资源List<CompletableFuture<Void>> tasks =newArrayList<>();for(int page = startPage; page <= endPage; page++){ tasks.add(processPage(page));}CompletableFuture.allOf(tasks.toArray(newCompletableFuture[0])).join(); dbExecutor.shutdown(); dataSource.close();}publicstaticvoidmain(String[] args)throwsException{Instant startTime =Instant.now();newHttpClientCrawler().crawl(1,10);Duration elapsed =Duration.between(startTime,Instant.now());System.out.printf("总耗时:%d 秒 %d 毫秒%n", elapsed.toSecondsPart(), elapsed.toMillisPart());}}
Python vs Java 异步爬虫对比:

5.3 方案三:Selenium 自动化浏览器

当页面加密了 Ajax 接口、接口参数复杂难以复现,或者页面确实依赖 JavaScript 渲染时,我们需要驱动真实浏览器执行页面逻辑,再从渲染后的 DOM 中提取数据。

Selenium 是最成熟的浏览器自动化框架,支持 Chrome、Edge、Firefox 等主流浏览器。

使用前提: 需要下载与浏览器版本匹配的 WebDriver(如 EdgeDriver),并配置到系统路径中。

Python 示例(携带 Cookie 登录博客园):

from selenium import webdriver from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC # 启动 Edge 浏览器 driver = webdriver.Edge() driver.get("https://i.cnblogs.com/") driver.delete_all_cookies()# 解析并注入 Cookie(实现免密登录) cookie_str ="your_cookie_string_here" cookies = cookie_str.split("; ")for cookie in cookies: name, value = cookie.split("=",1) driver.add_cookie({"name": name,"value": value,"domain":".cnblogs.com"})# 注入 Cookie 后重新加载页面 driver.get("https://i.cnblogs.com/articles")# 等待页面元素加载完成(显式等待,避免直接 sleep) wait = WebDriverWait(driver,10) wait.until(EC.presence_of_all_elements_located((By.CSS_SELECTOR,"tr.post-list-item")))# 提取文章标题 titles = driver.find_elements(By.CSS_SELECTOR,"a.entry.post-title-appendix")for t in titles:print(t.text)

Java 示例(Selenium + EdgeDriver):

importorg.openqa.selenium.*;importorg.openqa.selenium.edge.EdgeDriver;importorg.openqa.selenium.support.ui.ExpectedConditions;importorg.openqa.selenium.support.ui.WebDriverWait;importjava.time.Duration;importjava.util.List;publicclassSeleniumCrawler{publicstaticvoidmain(String[] args){WebDriver driver =newEdgeDriver();try{ driver.get("https://i.cnblogs.com/"); driver.manage().deleteAllCookies();// 注入 CookieString cookieStr ="your_cookie_string_here";for(String item : cookieStr.split("; ")){String[] parts = item.split("=",2);if(parts.length ==2){ driver.manage().addCookie(newCookie.Builder(parts[0], parts[1]).domain(".cnblogs.com").path("/").build());}} driver.get("https://i.cnblogs.com/articles");// 显式等待WebDriverWait wait =newWebDriverWait(driver,Duration.ofSeconds(10)); wait.until(ExpectedConditions.presenceOfAllElementsLocatedBy(By.cssSelector("tr.post-list-item")));List<WebElement> titles = driver.findElements(By.cssSelector("a.entry.post-title-appendix"));System.out.println("文章标题:");for(WebElement title : titles){System.out.println(title.getText());}}finally{ driver.quit();}}}
Selenium 的注意事项:显式等待(WebDriverWait)优于隐式等待(implicitly_wait),更可控浏览器实例资源消耗较大,不适合高并发爬取,适合低频、需要登录状态的场景可结合无头模式(--headless)在服务器环境中运行,不弹出浏览器窗口

六、Java 逆向爬虫技术

6.1 什么是逆向爬虫?

在很多实际场景中,网站不仅仅是简单地通过 Ajax 返回数据,还会对请求参数进行加密、签名或动态生成 Token,以防止接口被直接调用。此时单纯抓取 Ajax 接口将无法直接使用,需要逆向分析前端 JavaScript 代码的加密逻辑,在爬虫端复现签名算法后才能成功请求。

逆向爬虫的核心思路:

分析网络请求 → 定位加密参数 → 阅读/调试 JS 代码 → 在 Java 中复现加密逻辑 → 构造合法请求 

6.2 常见的反爬加密手段

加密手段说明逆向难度
时间戳 + 签名请求参数中包含 timestamp 和基于密钥的 sign中等
Token 动态生成页面加载时 JS 动态计算 Token,每次请求携带中等
请求参数加密请求体或 URL 参数经过 Base64、AES、RSA 等加密较高
Cookie 反爬通过 JS 动态设置 Cookie,服务器校验 Cookie 合法性中等
字体反爬使用自定义字体映射,页面显示正常但源码中是乱码较高

6.3 逆向实战:复现签名算法

以下以一个典型的「时间戳 + HMAC-SHA256 签名」场景为例,演示如何在 Java 中复现前端的签名逻辑。

场景描述: 通过浏览器 DevTools 分析发现,接口请求头中包含以下自定义字段:

  • X-Timestamp:当前时间戳(毫秒)
  • X-Sign:基于请求路径、时间戳和密钥的 HMAC-SHA256 签名

前端 JavaScript 签名逻辑(简化版):

// 前端代码中定位到的签名函数functiongenerateSign(path, timestamp, secretKey){const message = path +"|"+ timestamp;return CryptoJS.HmacSHA256(message, secretKey).toString();}

Java 复现:

importjavax.crypto.Mac;importjavax.crypto.spec.SecretKeySpec;importjava.net.URI;importjava.net.http.HttpClient;importjava.net.http.HttpRequest;importjava.net.http.HttpResponse;importjava.nio.charset.StandardCharsets;importjava.time.Duration;publicclassReverseCrawler{// 通过逆向 JS 代码获取到的密钥(通常硬编码在前端或由特定接口返回)privatestaticfinalStringSECRET_KEY="your_secret_key_from_js";/** * 复现前端 HMAC-SHA256 签名算法 */publicstaticStringgenerateSign(String path,long timestamp,String secretKey)throwsException{String message = path +"|"+ timestamp;Mac mac =Mac.getInstance("HmacSHA256");SecretKeySpec keySpec =newSecretKeySpec( secretKey.getBytes(StandardCharsets.UTF_8),"HmacSHA256"); mac.init(keySpec);byte[] hash = mac.doFinal(message.getBytes(StandardCharsets.UTF_8));// 转为十六进制字符串StringBuilder hexString =newStringBuilder();for(byte b : hash){String hex =Integer.toHexString(0xff& b);if(hex.length()==1) hexString.append('0'); hexString.append(hex);}return hexString.toString();}publicstaticvoidmain(String[] args)throwsException{String apiPath ="/api/data/list";String baseUrl ="https://example.com";long timestamp =System.currentTimeMillis();// 1. 生成签名String sign =generateSign(apiPath, timestamp,SECRET_KEY);// 2. 构造带签名的请求HttpClient client =HttpClient.newBuilder().connectTimeout(Duration.ofSeconds(10)).build();HttpRequest request =HttpRequest.newBuilder().uri(URI.create(baseUrl + apiPath)).header("User-Agent","Mozilla/5.0").header("X-Timestamp",String.valueOf(timestamp)).header("X-Sign", sign).header("Accept","application/json").GET().build();// 3. 发送请求HttpResponse<String> response = client.send( request,HttpResponse.BodyHandlers.ofString());System.out.println("状态码:"+ response.statusCode());System.out.println("响应:"+ response.body());}}

6.4 逆向爬虫的常用工具与技巧

调试工具:

  • 浏览器 DevTools:Network 面板查看请求参数,Sources 面板断点调试 JS 代码
  • Charles / Fiddler:抓包工具,可以查看 HTTPS 请求的完整内容(需要安装证书)
  • 浏览器 Override 功能:可以替换线上 JS 文件进行调试,在关键位置插入 console.log 输出中间值

Java 中常用的加密工具库:

加密算法Java 实现方式
MD5 / SHA-256java.security.MessageDigest
HMAC-SHA256javax.crypto.Mac
AESjavax.crypto.Cipher + AES/CBC/PKCS5Padding
RSAjavax.crypto.Cipher + RSA/ECB/PKCS1Padding
Base64java.util.Base64

6.5 逆向爬虫的注意事项

  • 优先尝试 Ajax 接口:逆向是最后的手段,很多看似加密的接口实际上可以通过简单的参数分析直接调用
  • 关注 JS 混淆:生产环境的 JS 通常经过 Webpack 打包和混淆,需要使用美化工具(如 js-beautify)还原可读性
  • 密钥可能动态变化:部分网站的签名密钥会定期更换或从服务端动态获取,需要建立自动化的密钥更新机制
  • 合规性:逆向他人的加密算法可能涉及法律风险,仅应在合法合规的前提下用于公开数据的采集

七、反爬机制与应对策略总结

在实际爬虫开发中,目标网站通常会部署多种反爬手段来限制自动化访问。了解这些机制并掌握合理的应对策略,是爬虫工程师的必备技能。

反爬手段表现形式应对策略
User-Agent 检测拒绝非浏览器 UA 的请求设置随机 User-Agent 池
IP 频率限制同一 IP 短时间大量请求被封禁使用代理 IP 池,控制请求频率
验证码弹出图形/滑块验证码接入验证码识别服务或降低访问频率
Cookie / Session 校验要求携带有效 CookieSelenium 获取 Cookie 后注入
请求参数加密URL 或 Body 参数经过加密逆向 JS 代码复现加密逻辑
动态渲染数据由 JS 动态生成Selenium / Playwright 渲染
字体反爬自定义字体导致源码乱码解析字体文件建立映射表
Honeypot 陷阱隐藏链接诱导爬虫进入过滤 display:none 等隐藏元素
核心原则: 合理控制请求频率、遵守 robots.txt、不采集敏感非公开数据。技术上可以突破的限制,法律上不一定允许。始终在合法合规的前提下进行数据采集。

八、总结

本次技术分享从基础概念到实战应用,系统性地介绍了网络爬虫的核心技术栈:

  1. 基础篇:HTTP 请求与静态页面获取(requests / Apache HttpClient)
  2. 解析篇:正则、XPath、BeautifulSoup、pyquery、parsel、Jsoup 多方案对比
  3. 进阶篇:Ajax 接口爬取、异步高并发爬虫(Python asyncio / Java CompletableFuture)
  4. 自动化篇:Selenium 浏览器自动化
  5. 逆向篇:Java 逆向爬虫技术,复现前端签名算法
  6. 框架篇:Scrapy 工业级爬虫框架快速上手
  7. 防御篇:反爬机制识别与应对策略

在技术选型上,建议遵循以下优先级:Ajax 接口直接调用 > 异步批量爬取 > Scrapy 框架 > Selenium 自动化 > 逆向分析。优先选择轻量高效的方案,只在必要时才引入更重的工具。


本文仅供技术学习交流,请在合法合规的前提下使用爬虫技术。

Read more

一文读懂OpenRouter:全球AI模型的“超级接口”,很多免费模型

一文读懂OpenRouter:全球AI模型的“超级接口”,很多免费模型

在人工智能技术百花齐放的今天,开发者面临着一个“幸福的烦恼”:市面上有GPT-4、Claude、Gemini、Kimi、GLM等众多顶尖大模型,但每个平台都需要单独注册、管理API密钥、对接不同接口文档,极大地增加了开发成本与技术门槛。 OpenRouter的出现,正是为了解决这一痛点。它不仅是一个AI模型聚合平台,更被业界视为全球AI模型竞争的“风向标”。 1. 什么是OpenRouter? OpenRouter是一个开源的AI模型聚合平台,它像一个“超级接口”或“路由器”,将全球超过300个主流AI模型(来自400多个提供商)整合在一起,为开发者提供统一的API接口。 其核心价值在于: * 统一API接口:开发者只需使用一套API密钥,即可调用包括OpenAI、Anthropic、Google、以及中国头部厂商(如MiniMax、月之暗面、智谱AI)在内的所有模型,无需为每个模型单独适配接口。 * 智能路由与成本优化:平台支持智能路由,可自动匹配性价比最高的模型,或根据开发者需求手动切换。其采用纯按量付费模式,无月费或最低消费,价格通常与官方持平甚至更低。 * 零

By Ne0inhk
量化、算子融合、内存映射:C语言实现AI推理的“三板斧“

量化、算子融合、内存映射:C语言实现AI推理的“三板斧“

量化、算子融合、内存映射:C语言实现AI推理的"三板斧" 摘要:做嵌入式AI开发的同学,大概率都遇到过这样的困境:训练好的AI模型(比如CNN),在PC上用TensorFlow/PyTorch跑起来流畅丝滑,可移植到单片机、MCU等边缘设备上,要么内存爆掉,要么推理延迟高到无法使用——毕竟边缘设备的资源太有限了:几百KB的RAM、几MB的Flash、没有GPU加速,甚至连浮点运算都要靠软件模拟。这时,依赖庞大的深度学习框架就成了“杀鸡用牛刀”,甚至根本无法运行。而C语言,作为嵌入式开发的“母语”,凭借其极致的性能控制、内存可控性和无 runtime 依赖的优势,成为边缘设备AI推理引擎的最佳选择。但纯C语言实现AI推理,绝不是简单地“用C重写框架代码”,关键在于掌握三大核心优化技术——这就是我们今天要讲的AI推理“三板斧”:量化、算子融合、内存映射。 它们三者协同作用,能从“体积、速度、内存”三个维度彻底优化AI推理性能:

By Ne0inhk
会提问的人,正在用AI收割下一个十年

会提问的人,正在用AI收割下一个十年

文章目录 * 引言:一场关于AI的颠覆性对话 * 从对话到收入:AI时代的新型生产关系 * 会说话就能赚钱?这不是天方夜谭 * 从想法到产品:三天的魔法 * 技术民主化:AI不再是工程师的专属 * 打破技术壁垒的革命 * 文科生的优势在哪里? * AI时代的商业逻辑:用户付费意愿超预期 * 价值认知的转变 * 为什么用户愿意付费? * 新的商业模式 * AI的边界:思考仍然是人类的专属 * 技术的局限性 * 人机协作的最佳模式 * 实践指南:如何开始你的AI创作之旅 * 第一步:转变思维方式 * 第二步:从小项目开始 * 第三步:快速迭代 * 第四步:关注用户价值 * 第五步:建立商业模式 * 《脉向AI》:探索AI时代的无限可能 * 为什么要关注这期访谈? * 这不仅仅是一次访谈 * 结语:属于每个人的AI时代 引言:一场关于AI的颠覆性对话 在这个技术迅猛发展的时代,我们总是习惯性地认为,掌握AI技术是程序员和工程师的专属特权。但如果我告诉你,文科生可能才是A

By Ne0inhk
OpenClaw保姆级教程:打造你的24小时AI全能助理

OpenClaw保姆级教程:打造你的24小时AI全能助理

OpenClaw保姆级教程:打造你的24小时AI全能助理 最近AI圈被一个叫OpenClaw的工具刷爆了,圈内人都喊它**“小龙虾”**。 有人靠它卧床14天搭建8个AI智能体替自己办公,除夕夜自动给611人发个性化拜年文案,还产出6篇公众号内容、做了播放量30万+的短视频;有人用它处理工作邮件,每天直接省出25分钟,效率翻了20倍。 这哪里是简单的AI工具——分明是能24小时替你干活、越用越懂你的全能个人助理! 关键是它开源免费,最低7.9元就能起步体验。不管你是职场打工人想提升效率,还是创业者想打造“一人公司”,甚至是纯新手不懂技术,看完这篇保姆级教程,都能轻松拿捏“养龙虾”的秘诀,让AI成为你的生产力杠杆! 官网地址:https://github.com/openclaw/openclaw 官网文档:https://docs.openclaw.ai/zh-CN/gateway 一、先搞懂:OpenClaw到底牛在哪?为什么全网都在养“龙虾”? 提起AI个人助理,很多人都踩过坑——看似功能多,实际笨手笨脚,

By Ne0inhk