Zzzxb's Blog

你要静心学习那份等待时机的成熟的情绪,也要你一定保有这份等待之外的努力和坚持。

Centos7 无头浏览器截图 Feb 27, 2026

你的系统是 CentOS 7,glibc 最高 2.17,libstdc++ 也很旧。所以:

  1. Node.js 14 - 完全兼容 CentOS 7
  2. Chromium - EPEL 源版本兼容 CentOS 7
  3. Puppeteer-core 19.x - 兼容 Node.js 14

1.安装Node

官方提供

# 下载并安装 nvm:
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# 代替重启 shell
\. "$HOME/.nvm/nvm.sh"
# 下载并安装 Node.js:
nvm install 14
# 验证 Node.js 版本:
node -v # Should print "v14.21.3".
# 验证 npm 版本:
npm -v # Should print "6.14.18".

如果已经存在其他版本Node

# 1. 完全卸载当前 Node.js
nvm deactivate
# 你自己的版本
nvm uninstall v16.20.2

# 2. 清理 NVM 缓存
nvm cache clear

# 3. 安装与 CentOS 7 完全兼容的 Node.js 14
nvm install 14.21.3
nvm use 14.21.3
nvm alias default 14.21.3

# 4. 验证
node --version
npm --version

如果 NVM 安装失败,直接下载二进制包

# 下载预编译的 Node.js 14(CentOS 7 兼容版)
cd /tmp
wget https://nodejs.org/dist/v14.21.3/node-v14.21.3-linux-x64.tar.xz
tar -xf node-v14.21.3-linux-x64.tar.xz
sudo cp -r node-v14.21.3-linux-x64/* /usr/local/

# 验证
/usr/local/bin/node --version

2. 下载浏览器

# 放弃 Chrome,使用 Chromium 旧版本
# 1. 安装 EPEL 源(如果还没装)
yum install -y epel-release

# 2. 安装 Chromium(EPEL 源提供的是与 CentOS 7 兼容的旧版本)
yum install -y chromium

# 3. 确认安装路径
which chromium-browser
# 通常是 /usr/bin/chromium-browser

3. 安装模块

# 1. 确保 Node.js 14 可用
node --version  # 应该是 v14.21.3

# 2. 清理项目,填写自己的项目路径
cd /browser_screenshot
rm -rf node_modules package-lock.json

# 3. 重新创建 package.json,根据自己项目修改
cat > package.json << 'EOF'
{
  "name": "browser_screenshot",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "node test.js"
  },
  "dependencies": {
    "puppeteer-core": "19.11.1"
  }
}
EOF

# 4. 设置跳过下载
export PUPPETEER_SKIP_DOWNLOAD=true
export PUPPETEER_SKIP_CHROMIUM_DOWNLOAD=true

# 5. 安装
npm install

字体乱码

最快的临时解决方案

# 安装文泉驿字体(最简单)
sudo yum install -y wqy-microhei-fonts wqy-zenhei-fonts
sudo fc-cache -fv

# 然后重新运行你的脚本

######################### 或者
# 1. 先修缓存(免费)
sudo fc-cache -fv

# 2. 如果还乱码,装文鼎(30秒)
sudo yum install -y arphic-ukai-fonts arphic-uming-fonts
sudo fc-cache -fv

# 3. 再不行,直接上思源黑体
sudo yum install -y google-noto-sans-cjk-ttc-fonts
sudo fc-cache -fv

✅ 方案一:文鼎 PL 系列(已明确2022年重申免费商用)

这是最稳妥的开源备选,文鼎官方2022年明确公告:四款1999版PL字体永久免费商用,且授权宽松度与文泉驿相同 。

# CentOS 7 直接安装(EPEL源已包含)
sudo yum install -y arphic-ukai-fonts arphic-uming-fonts

# 或者手动下载(官方提供TTF)
cd /tmp
wget http://ftp.gnu.org/non-gnu/chinese-fonts-truetype/arphic/ckai.ttf  # 文鼎PL中楷
wget http://ftp.gnu.org/non-gnu/chinese-fonts-truetype/arphic/cbsong.ttf # 文鼎PL简报宋
sudo cp *.ttf /usr/share/fonts/chinese/

✅ 方案二:思源系列(SIL开源,最全字符集)

# 安装思源黑体(Google Noto Sans CJK)
sudo yum install -y google-noto-sans-cjk-ttc-fonts

# 安装思源宋体(如果源里有)
sudo yum install -y google-noto-serif-cjk-ttc-fonts

如果你的yum源没有,手动下载也很简单(字体文件约50MB):

cd /tmp
# 思源黑体 Normal 2.004(官方GitHub)
wget https://github.com/adobe-fonts/source-han-sans/releases/download/2.004R/SourceHanSansHWSC.zip
unzip SourceHanSansHWSC.zip
sudo cp SubsetTTF/*.ttf /usr/share/fonts/chinese/

✅ 方案三:Ubuntu 字体(自由字体,可用)

Ubuntu 官方字体包含完整中文字形,同样开源。

sudo yum install -y ubuntu-font-family
# 或手动下载
wget https://assets.ubuntu.com/v1/0cef8205-ubuntu-font-family-0.83.zip

✅ 方案四:Noto Sans SC(Google 官方中文变体)

如果你想要简体字形优先,Noto Sans SC 是最干净的选择

sudo yum install -y google-noto-sans-sc-fonts

脚本

const puppeteer = require('puppeteer');
const fs = require('fs');
const path = require('path');
const crypto = require('crypto');

// ====================== 动态参数生成器 ======================
const DynamicGenerator = {
  // 生成随机字符串
  randomString(length = 8) {
    return crypto.randomBytes(length).toString('hex').slice(0, length);
  },

  // 生成随机数字字符串
  randomNumberString(length = 10) {
    return Math.floor(Math.random() * Math.pow(10, length)).toString().padStart(length, '0');
  },

  // 生成随机时间戳
  randomTimestamp() {
    return Math.floor(Date.now() / 1000) + Math.floor(Math.random() * 1000);
  },

  // 生成随机IP
  randomIP() {
    return `${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}.${Math.floor(Math.random() * 255)}`;
  },

  // 生成随机User-Agent
  randomUserAgent() {
    const userAgents = [
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/119.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
      'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/118.0.0.0 Safari/537.36 Edg/118.0.2088.76',
      'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.1 Safari/605.1.15',
    ];
    return userAgents[Math.floor(Math.random() * userAgents.length)];
  },

  // 生成随机Accept-Language
  randomAcceptLanguage() {
    const languages = [
      'zh-CN,zh;q=0.9,en;q=0.8',
      'zh-CN,zh;q=0.9,en;q=0.8,zh-TW;q=0.7',
      'zh-CN,zh;q=0.9,en-US;q=0.8,en;q=0.7',
      'en-US,en;q=0.9,zh-CN;q=0.8,zh;q=0.7',
      'zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7',
    ];
    return languages[Math.floor(Math.random() * languages.length)];
  },

  // 生成随机Accept-Encoding
  randomAcceptEncoding() {
    const encodings = [
      'gzip, deflate, br',
      'gzip, deflate',
      'gzip, deflate, br, zstd',
      'gzip, deflate, br, compress',
    ];
    return encodings[Math.floor(Math.random() * encodings.length)];
  },

  // 生成随机Connection
  randomConnection() {
    return Math.random() > 0.5 ? 'keep-alive' : 'close';
  },

  // 生成随机Sec-Ch-Ua
  randomSecChUa() {
    const versions = [
      '"Not_A Brand";v="8", "Chromium";v="120", "Google Chrome";v="120"',
      '"Not_A Brand";v="8", "Chromium";v="119", "Google Chrome";v="119"',
      '"Not_A Brand";v="8", "Chromium";v="118", "Microsoft Edge";v="118"',
      '"Not_A Brand";v="8", "Chromium";v="120", "Safari";v="17.1"',
    ];
    return versions[Math.floor(Math.random() * versions.length)];
  },

  // 生成随机Sec-Ch-Ua-Platform
  randomSecChUaPlatform() {
    const platforms = ['"Windows"', '"macOS"', '"Linux"'];
    return platforms[Math.floor(Math.random() * platforms.length)];
  },

  // 生成随机Sec-Ch-Ua-Mobile
  randomSecChUaMobile() {
    return Math.random() > 0.9 ? '?1' : '?0';
  },

  // 生成百度Cookie
  generateBaiduCookies() {
    const timestamp = Date.now();
    const randomId = this.randomString(16);
    const pstm = this.randomTimestamp();
    const bduid = `${this.randomString(32).toUpperCase()}:FG=1`;
    
    return {
      'BAIDUID': bduid,
      'BIDUPSID': this.randomString(32).toUpperCase(),
      'PSTM': pstm,
      'BDUSS': this.randomString(192),
      '__yjs_duid': `${this.randomNumberString(17)}${this.randomNumberString(5)}`,
      'BDSVRTM': Math.floor(Math.random() * 10).toString(),
      'H_PS_PSSID': `${this.randomNumberString(4)}_${this.randomNumberString(4)}_${this.randomNumberString(3)}`,
      'delPer': Math.random() > 0.5 ? '0' : '1',
      'BD_CK_SAM': Math.random() > 0.5 ? '1' : '0',
      'BD_HOME': Math.random() > 0.5 ? '1' : '0',
      'BDRCVFR': `${this.randomString(8).toUpperCase()}`,
    };
  },

  // 生成Cookie字符串
  generateCookieString() {
    const cookies = this.generateBaiduCookies();
    return Object.entries(cookies)
      .map(([key, value]) => `${key}=${value}`)
      .join('; ');
  },

  // 生成随机X-Request-Id
  generateRequestId() {
    return `${this.randomString(8)}-${this.randomString(4)}-${this.randomString(4)}-${this.randomString(4)}-${this.randomString(12)}`;
  },

  // 生成随机Viewport尺寸
  randomViewport() {
    const widths = [1366, 1440, 1536, 1600, 1680, 1920, 2560];
    const heights = [768, 800, 864, 900, 1050, 1200, 1440];
    return {
      width: widths[Math.floor(Math.random() * widths.length)],
      height: heights[Math.floor(Math.random() * heights.length)],
    };
  },

  // 生成随机滚动行为
  randomScrollBehavior() {
    return Math.random() > 0.7 ? 'smooth' : 'auto';
  },

  // 生成随机等待时间
  randomWaitTime(base = 1000) {
    return base + Math.floor(Math.random() * 2000);
  },

  // 生成随机屏幕缩放
  randomDeviceScaleFactor() {
    const scales = [1, 1.25, 1.5, 1.75, 2];
    return scales[Math.floor(Math.random() * scales.length)];
  },

  // 生成随机Referer
  randomReferer() {
    const referers = [
      'https://www.baidu.com/',
      'https://www.baidu.com/s?wd=',
      'https://www.baidu.com/index.php',
      'https://m.baidu.com/',
      'https://www.baidu.com/?tn=',
    ];
    return referers[Math.floor(Math.random() * referers.length)];
  },

  // 生成随机屏幕颜色深度
  randomColorDepth() {
    return Math.random() > 0.5 ? 24 : 30;
  },

  // 生成随机时区
  randomTimezone() {
    const timezones = [
      'Asia/Shanghai',
      'Asia/Hong_Kong',
      'Asia/Taipei',
      'Asia/Tokyo',
      'Asia/Singapore',
    ];
    return timezones[Math.floor(Math.random() * timezones.length)];
  }
};

// ====================== 可配置参数 ======================
const CONFIG = {
  search: {
    url: process.argv[2] ? `https://www.baidu.com/s?wd=${encodeURIComponent(process.argv[2])}` : 'https://www.baidu.com/s?wd=联动优势',
    targetText: process.argv[3] || '文心智能体·AI生成',
  },
  screenshot: {
    width: 1920,
    height: 800,
    type: 'jpeg',
    quality: 85,
  },
  elementPosition: {
    topMargin: 120,
    mode: 'center',
    customPositionPercent: 10,
  },
  browser: {
    headless: 'new',
    windowWidth: 1920,
    windowHeight: 800,
    userAgent: DynamicGenerator.randomUserAgent(),
  },
  output: {
    directory: process.argv[4] || 'screenshots',
    filenamePrefix: process.argv[2] ? crypto.createHash('md5').update(process.argv[2] + Date.now()).digest('hex') : `截图_${Date.now()}`,
    saveLogFile: false,
    saveFullPageScreenshot: false,
  },
  behavior: {
    waitAfterLoad: DynamicGenerator.randomWaitTime(2000),
    waitAfterScroll: DynamicGenerator.randomWaitTime(500),
    highlightElement: false,
    highlightColor: 'red',
    addMarkerLabel: false,
    timeout: 30000,
    scrollBehavior: DynamicGenerator.randomScrollBehavior(),
  },
};

// ====================== 主程序 ======================
(async () => {
  let browser = null;
  
  try {
    // 每次请求重新生成动态参数
    const dynamicUA = DynamicGenerator.randomUserAgent();
    const dynamicViewport = DynamicGenerator.randomViewport();
    const dynamicCookies = DynamicGenerator.generateCookieString();
    const dynamicReferer = DynamicGenerator.randomReferer();
    const dynamicRequestId = DynamicGenerator.generateRequestId();
    const dynamicAcceptLang = DynamicGenerator.randomAcceptLanguage();
    const dynamicAcceptEncoding = DynamicGenerator.randomAcceptEncoding();
    const dynamicConnection = DynamicGenerator.randomConnection();
    const dynamicSecChUa = DynamicGenerator.randomSecChUa();
    const dynamicSecChUaPlatform = DynamicGenerator.randomSecChUaPlatform();
    const dynamicSecChUaMobile = DynamicGenerator.randomSecChUaMobile();
    const dynamicColorDepth = DynamicGenerator.randomColorDepth();
    const dynamicTimezone = DynamicGenerator.randomTimezone();
    const dynamicDeviceScaleFactor = DynamicGenerator.randomDeviceScaleFactor();
    
    // 更新配置
    CONFIG.browser.userAgent = dynamicUA;
    CONFIG.behavior.waitAfterLoad = DynamicGenerator.randomWaitTime(2000);
    CONFIG.behavior.waitAfterScroll = DynamicGenerator.randomWaitTime(500);
    CONFIG.behavior.scrollBehavior = DynamicGenerator.randomScrollBehavior();
    CONFIG.output.filenamePrefix = process.argv[2] 
      ? crypto.createHash('md5').update(process.argv[2] + Date.now() + Math.random()).digest('hex') 
      : `截图_${Date.now()}_${DynamicGenerator.randomString(4)}`;
    
    fs.mkdirSync(CONFIG.output.directory, { recursive: true });
    
    browser = await puppeteer.launch({
      headless: CONFIG.browser.headless,
      args: [
        '--no-sandbox',
        '--disable-setuid-sandbox',
        '--disable-blink-features=AutomationControlled',
        '--disable-gpu',
        '--no-first-run',
        '--disable-dev-shm-usage',
        `--window-size=${dynamicViewport.width},${dynamicViewport.height}`,
        `--window-position=${Math.floor(Math.random() * 100)},${Math.floor(Math.random() * 100)}`,
        `--lang=${dynamicAcceptLang.split(',')[0]}`,
        '--disable-features=IsolateOrigins,site-per-process',
        '--disable-web-security',
        '--disable-features=BlockInsecurePrivateNetworkRequests',
      ],
      defaultViewport: {
        width: dynamicViewport.width,
        height: dynamicViewport.height,
        deviceScaleFactor: dynamicDeviceScaleFactor,
        isMobile: dynamicSecChUaMobile === '?1',
        hasTouch: dynamicSecChUaMobile === '?1',
      },
      ignoreHTTPSErrors: true,
    });
    
    const page = await browser.newPage();
    
    // 设置动态User-Agent
    await page.setUserAgent(dynamicUA);
    
    // 设置动态Viewport
    await page.setViewport({ 
      width: dynamicViewport.width, 
      height: dynamicViewport.height,
      deviceScaleFactor: dynamicDeviceScaleFactor,
    });
    
    // 设置动态Cookie
    await page.setCookie(...dynamicCookies.split('; ').map(cookie => {
      const [name, value] = cookie.split('=');
      return {
        name,
        value,
        domain: '.baidu.com',
        path: '/',
        secure: Math.random() > 0.5,
        httpOnly: Math.random() > 0.5,
        sameSite: ['Strict', 'Lax', 'None'][Math.floor(Math.random() * 3)],
      };
    }));
    
    // 设置动态Headers
    await page.setExtraHTTPHeaders({
      'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8',
      'Accept-Language': dynamicAcceptLang,
      'Accept-Encoding': dynamicAcceptEncoding,
      'Connection': dynamicConnection,
      'Upgrade-Insecure-Requests': Math.random() > 0.5 ? '1' : '0',
      'Sec-Ch-Ua': dynamicSecChUa,
      'Sec-Ch-Ua-Platform': dynamicSecChUaPlatform,
      'Sec-Ch-Ua-Mobile': dynamicSecChUaMobile,
      'Sec-Fetch-Site': ['none', 'cross-site', 'same-origin', 'same-site'][Math.floor(Math.random() * 4)],
      'Sec-Fetch-Mode': 'navigate',
      'Sec-Fetch-User': '?1',
      'Sec-Fetch-Dest': 'document',
      'X-Request-Id': dynamicRequestId,
      'X-Forwarded-For': DynamicGenerator.randomIP(),
      'X-Real-IP': DynamicGenerator.randomIP(),
      'Referer': dynamicReferer,
      'Origin': 'https://www.baidu.com',
      'Cache-Control': ['max-age=0', 'no-cache', 'no-store'][Math.floor(Math.random() * 3)],
      'DNT': Math.random() > 0.5 ? '1' : '0',
    });
    
    // 动态浏览器特征
    await page.evaluateOnNewDocument((dynamicTimezone, dynamicColorDepth) => {
      // 修改webdriver属性
      Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
      
      // 修改插件
      Object.defineProperty(navigator, 'plugins', { 
        get: () => [1, 2, 3, 4, 5].map(() => ({ name: 'Chrome PDF Plugin' }))
      });
      
      // 修改语言
      Object.defineProperty(navigator, 'languages', { get: () => ['zh-CN', 'zh', 'en'] });
      
      // 修改时区
      Object.defineProperty(Intl.DateTimeFormat.prototype, 'resolvedOptions', {
        value: function() {
          const result = originalResolvedOptions.call(this);
          result.timeZone = dynamicTimezone;
          return result;
        }
      });
      
      // 修改屏幕属性
      Object.defineProperty(screen, 'colorDepth', { get: () => dynamicColorDepth });
      Object.defineProperty(screen, 'pixelDepth', { get: () => dynamicColorDepth });
      
      // 修改硬件并发数
      Object.defineProperty(navigator, 'hardwareConcurrency', { get: () => [4, 6, 8, 12][Math.floor(Math.random() * 4)] });
      
      // 修改设备内存
      Object.defineProperty(navigator, 'deviceMemory', { get: () => [4, 8, 16][Math.floor(Math.random() * 3)] });
      
      // 添加随机Canvas指纹
      const originalToDataURL = HTMLCanvasElement.prototype.toDataURL;
      HTMLCanvasElement.prototype.toDataURL = function(type) {
        const result = originalToDataURL.apply(this, arguments);
        if (type === 'image/png' || !type) {
          // 添加微小扰动
          return result.replace(/[a-f0-9]{10,}/g, (match) => {
            return match + Math.random().toString(16).substring(2, 4);
          });
        }
        return result;
      };
    }, dynamicTimezone, dynamicColorDepth);
    
    // 动态等待时间
    await new Promise(resolve => setTimeout(resolve, CONFIG.behavior.waitAfterLoad));
    
    // 访问页面
    await page.goto(CONFIG.search.url, { 
      waitUntil: 'networkidle2',
      timeout: CONFIG.behavior.timeout 
    });
    
    await page.waitForFunction(() => {
      return document.readyState === 'complete';
    }, { timeout: 10000 });
    
    // 动态滚动
    await page.evaluate((y, behavior) => {
      window.scrollTo({ top: y, behavior: behavior });
    }, Math.floor(Math.random() * 300), CONFIG.behavior.scrollBehavior);
    
    await new Promise(resolve => setTimeout(resolve, CONFIG.behavior.waitAfterLoad));
    
    // ====================== 优化的元素查找逻辑 ======================
    const elementInfo = await page.evaluate((searchText, screenshotHeight, mode, topMargin, customPercent) => {
      const getFullText = (element) => {
        if (!element) return '';
        let text = element.textContent || element.innerText || '';
        if (text.length < 5) {
          text = element.getAttribute('title') || element.getAttribute('alt') || text;
        }
        return text.trim();
      };

      const cleanTextForMatch = (text) => {
        if (!text) return '';
        return text.replace(/\s+/g, '').toLowerCase();
      };

      const findElementByOptimizedStrategy = () => {
        // 策略1: 精确匹配指定标签结构
        try {
          const exactSpans = document.querySelectorAll('span.cosc-source-text.cos-line-clamp-1');
          for (const span of exactSpans) {
            const text = getFullText(span);
            if (text === searchText || text.includes(searchText)) {
              const rect = span.getBoundingClientRect();
              if (rect.width > 0 && rect.height > 0) {
                return span;
              }
            }
          }
        } catch (e) {}

        // 策略2: 模糊匹配指定标签结构
        try {
          const sourceSpans = document.querySelectorAll('span.cosc-source-text');
          for (const span of sourceSpans) {
            const text = getFullText(span);
            if (text.includes(searchText) || searchText.includes(text)) {
              const rect = span.getBoundingClientRect();
              if (rect.width > 0 && rect.height > 0) {
                return span;
              }
            }
          }
        } catch (e) {}

        // 策略3: 其他CSS选择器匹配
        const selectors = [
          '.cosc-source-text',
          '.common_17ggi',
          '.cosc-title-slot',
          '.common-content_6I4X7',
          '.cosc-card-content',
          '.agent-wrapper_Ycxpf',
          '.content_48ora',
          '[srcid="60601"]',
          '[tpl="ai_agent_distribute"]',
          '.result-op.c-container.new-pmd',
        ];
        
        for (const selector of selectors) {
          try {
            const elements = document.querySelectorAll(selector);
            for (const element of elements) {
              const text = getFullText(element);
              if (text.includes(searchText) || searchText.includes(text)) {
                const rect = element.getBoundingClientRect();
                if (rect.width > 0 && rect.height > 0) {
                  return element;
                }
              }
            }
          } catch (e) {}
        }

        // 策略4: 文本内容匹配
        const searchTextLower = searchText.toLowerCase();
        const searchTextClean = cleanTextForMatch(searchText);
        const allElements = document.querySelectorAll('*');
        
        let bestMatch = null;
        let bestScore = 0;
        
        for (const element of allElements) {
          const text = getFullText(element);
          if (!text || text.length < 3) continue;
          
          const rect = element.getBoundingClientRect();
          if (rect.width <= 0 || rect.height <= 0 || rect.top < 0) continue;
          
          let score = 0;
          const textLower = text.toLowerCase();
          const textClean = cleanTextForMatch(text);
          
          if (textLower === searchTextLower) {
            score = 100;
          } else if (textLower.includes(searchTextLower)) {
            score = 80;
          } else if (textClean.includes(searchTextClean)) {
            score = 70;
          } else if (searchTextLower.includes(textLower) && text.length >= 5) {
            score = 60;
          }
          
          const className = (element.className || '').toString();
          if (className.includes('cosc-') || 
              className.includes('common_') || 
              className.includes('agent') ||
              className.includes('AI') ||
              className.includes('智能')) {
            score += 30;
          }
          
          if (score > bestScore) {
            bestScore = score;
            bestMatch = element;
          }
        }
        
        if (bestMatch && bestScore >= 60) {
          return bestMatch;
        }

        return null;
      };
      
      let element = findElementByOptimizedStrategy();
      
      // 最终保底
      if (!element) {
        const keywords = ['文心智能体', 'AI生成', '智能分身', '联动优势', searchText];
        const allElements = document.querySelectorAll('*');
        for (const el of allElements) {
          const text = getFullText(el);
          for (const keyword of keywords) {
            if (text && text.includes(keyword)) {
              const rect = el.getBoundingClientRect();
              if (rect.width > 0 && rect.height > 0) {
                element = el;
                break;
              }
            }
          }
          if (element) break;
        }
      }
      
      if (!element) {
        return { found: false };
      }
      
      const rect = element.getBoundingClientRect();
      const scrollY = window.scrollY;
      
      let scrollToY;
      switch (mode) {
        case 'top':
          scrollToY = Math.max(0, (rect.top + scrollY) - topMargin);
          break;
        case 'center':
          const centerOffset = (screenshotHeight / 2) - (rect.height / 2);
          scrollToY = Math.max(0, (rect.top + scrollY) - centerOffset);
          break;
        case 'custom':
          const customPixelPosition = (screenshotHeight * customPercent) / 100;
          scrollToY = Math.max(0, (rect.top + scrollY) - customPixelPosition + topMargin);
          break;
        default:
          scrollToY = Math.max(0, (rect.top + scrollY) - topMargin);
      }
      
      const isExactMatch = element.tagName === 'SPAN' && 
                          (element.className || '').includes('cosc-source-text');
      
      return {
        found: true,
        elementTop: rect.top + scrollY,
        top: rect.top + scrollY,
        left: rect.left,
        width: rect.width,
        height: rect.height,
        scrollToY: scrollToY,
        elementText: getFullText(element).substring(0, 100),
        tagName: element.tagName,
        className: element.className || '',
        id: element.id || '',
        isExactMatch: isExactMatch,
        matchStrategy: isExactMatch ? 'exact_span_match' : 'fuzzy_match'
      };
      
    }, CONFIG.search.targetText, CONFIG.screenshot.height, CONFIG.elementPosition.mode, 
       CONFIG.elementPosition.topMargin, CONFIG.elementPosition.customPositionPercent);
    
    if (!elementInfo.found) {
      const result = { code: "2", msg: "未找到匹配内容" };
      console.log(JSON.stringify(result));
      await browser.close();
      process.exit(0);
      return;
    }
    
    // 动态滚动到位置
    await page.evaluate((y, behavior) => {
      window.scrollTo({ top: y, behavior: behavior });
    }, elementInfo.scrollToY, CONFIG.behavior.scrollBehavior);
    
    await new Promise(resolve => setTimeout(resolve, CONFIG.behavior.waitAfterScroll));
    
    const currentScrollY = await page.evaluate(() => window.scrollY);
    
    // 动态文件名
    const screenshotPath = path.join(
      CONFIG.output.directory, 
      `${CONFIG.output.filenamePrefix}.${CONFIG.screenshot.type}`
    );
    
    // 截图
    await page.screenshot({
      path: screenshotPath,
      clip: {
        x: Math.floor(Math.random() * 5), // 微小偏移,更自然
        y: currentScrollY + Math.floor(Math.random() * 3) - 1,
        width: CONFIG.screenshot.width,
        height: CONFIG.screenshot.height
      },
      type: CONFIG.screenshot.type,
      quality: CONFIG.screenshot.quality
    });
    
    let fileSizeKB = 0;
    try {
      const stats = fs.statSync(screenshotPath);
      fileSizeKB = Math.round(stats.size / 1024);
    } catch (e) {}
    
    const result = {
      code: "1",
      msg: "成功",
      data: path.resolve(screenshotPath),
      fileSize: fileSizeKB,
      elementInfo: {
        text: elementInfo.elementText,
        position: Math.round(elementInfo.elementTop),
        tag: elementInfo.tagName,
        className: elementInfo.className,
        exactMatch: elementInfo.isExactMatch || false
      },
      requestId: dynamicRequestId, // 返回请求ID便于追踪
      timestamp: Date.now()
    };
    
    console.log(JSON.stringify(result));
    
    await browser.close();
    process.exit(0);
    
  } catch (error) {
    const result = {
      code: "0",
      msg: `错误: ${error.message}`
    };
    console.log(JSON.stringify(result));
    
    if (browser) {
      try {
        await browser.close();
      } catch (e) {}
    }
    process.exit(1);
  }
})();