// 抓取器:抓厂家树/系列规格/价格,按新服务布局落盘(断点续抓,多线程安全)
// 两种价格通道:直连 dq123(默认) / 经内置旧代理 localhost:8081(带会话伪装,命中率高——工具自动启动它)
// 8081 是工具专用端口:node 新服务(元件库网页)占了 8080,两者须能同时跑
// 种子模式:从旧代理 proxy_cache 提取真实查询过的组合(sid→bg 列表),优先按种子形状抓价(重放+单变化)
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
namespace yjktool
{
internal class 抓取器
{
// 域名映射照旧 python 代理
private const string 电数据 = "https://dian.dq123.com/electricData?v=";
private const string 价格口 = "https://dx2.dhub.com.cn/pcprice/default/dx2/interface/webGetPrice?infoJson=";
// 经旧代理(路径同源,代理按前缀转发到真实域名并注入会话头)
private const string 代电数据 = "http://127.0.0.1:8081/electricData?v=";
private const string 代价格口 = "http://127.0.0.1:8081/dx2/pcprice/default/dx2/interface/webGetPrice?infoJson=";
public string 输出根 { get; private set; }
private string ZipDir { get { return Path.Combine(输出根, "dian", "electricData_zips"); } }
private string 价Dir { get { return Path.Combine(输出根, "dx2_webGetPrice"); } }
/// 价格请求是否经旧代理(8081)。树/系列包恒直连(静态资源无会话要求)。
public bool 经旧代理;
/// 种子组合:旧代理缓存里真实查询过的 (系列→组合串列表),价格抓取优先按种子形状。
/// 种子表:sid → [bg组合串, t历史时间戳] 列表。
/// 终版机制(实测):命中 = 系列在有效时刻 t 查过(服务器按 sid+t 记录) AND 组合是真实型号——
/// 系列只要有一个种子的 t,即可解锁它全部规格组组合的价格查询;t 用当前时间必返空。
private Dictionary> 种子 = new Dictionary>();
private int 无t系列数;
private int 无t提示过;
// 进度/日志回调(UI 线程调度由调用方处理)
public Action 日志 { get; set; }
public Action 进度 { get; set; } // (当前,总数)
public volatile bool 停止;
public 抓取器(string 输出目录)
{
输出根 = 输出目录;
Directory.CreateDirectory(ZipDir);
Directory.CreateDirectory(价Dir);
// net48 下显式启用 TLS1.2(dq123 全站 https,不设会握手失败)
try { ServicePointManager.SecurityProtocol = ServicePointManager.SecurityProtocol | SecurityProtocolType.Tls12; }
catch { }
ServicePointManager.DefaultConnectionLimit = 32;
}
/// 从缓存/数据目录加载种子(webGetPrice 响应里的 bg 组合串+原始 t 时间戳)。
/// 可多处调用合并(旧代理缓存+元件库网页 data 的现成真价都吃)。返回种子系列数。
public int 加载种子(string 目录)
{
if (种子 == null) 种子 = new Dictionary>();
try
{
foreach (string f in Directory.GetFiles(目录, "*webGetPrice*.data"))
{
try
{
string body = File.ReadAllText(f);
foreach (System.Text.RegularExpressions.Match m in
System.Text.RegularExpressions.Regex.Matches(body, "\"content\"\\s*:\\s*\"([A-Za-z0-9+/=]+)\""))
{
string decoded;
try { decoded = Encoding.UTF8.GetString(Convert.FromBase64String(m.Groups[1].Value)); }
catch { continue; }
if (string.IsNullOrEmpty(decoded) || decoded.Trim() == "[]") continue;
// ★ JavaScriptSerializer.Deserialize