首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> VC >> 關於VC++

網頁選取內容采集全攻略:IE 右鍵選單擴展 + ATL 組件實現 DOM 解析與資源下載

     編輯:關於VC++

核心原理四步驟

  1. IE 選單擴展:在 HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt 建立項目,預設值指向腳本頁面 URL。
  2. 情境控制:Contexts DWORD 值決定右鍵點擊何種元素時顯示 (位元組合):預設(0x1)、圖片(0x2)、控制項(0x4)、表單(0x8)、選取文字(0x10)、錨點(0x20)、超連結(0x40)。例:圖片+超連結 = 0x22。
  3. 腳本頁面:建立 COM 物件並呼叫方法,傳遞 external.menuArguments.document (嘗試失敗,改用視窗枚舉法)。
  4. ATL DLL 組件:核心邏輯 —— 取得前景視窗 → 枚舉 IE 子視窗 → 取得 IHTMLDocument2 → 解析選取範圍 → 下載圖片資源 → 修正連結 → 儲存本地。

註冊表結構


// 選單項
HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\MenuExt\「我的采集工具」
  預設值 = "file:///C:/Path/To/Script.htm"
  Contexts = dword:00000022  ; 圖片+錨點

// 解除安裝資訊
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\程序員加油站
  ArticlePath = "儲存目錄" (字串)
  ArticleNumber = 0 (DWORD, 遞增序號)

腳本頁面



ATL 組件核心實作:GetHtmlText()


STDMETHODIMP Ctesta::GetHtmlText() {
  char chFilePath[MAX_PATH];
  DWORD Number = 0;
  CRegistry reg;
  reg.Open(HKEY_LOCAL_MACHINE, "SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Uninstall\\程序員加油站\\");
  if(!reg.ReadDWORD("ArticleNumber", &Number) || !reg.ReadString("ArticlePath", chFilePath))
    return S_FALSE;

  HWND hWnd = GetActiveWindow(); // 取得前景視窗
  CoInitialize(NULL);

  // 載入 MSAA (OLEACC.DLL) 取得 ObjectFromLresult
  HINSTANCE hInst = ::LoadLibrary(_T("OLEACC.DLL"));
  if(hInst && hWnd) {
    HWND hWndChild = NULL;
    ::EnumChildWindows(hWnd, EnumChildProc, (LPARAM)&hWndChild); // 找 IE 子視窗
    if(hWndChild) {
      CComPtr pHTMLDoc;
      LRESULT lRes;
      UINT nMsg = ::RegisterWindowMessage(_T("WM_HTML_GETOBJECT"));
      ::SendMessageTimeout(hWndChild, nMsg, 0, 0, SMTO_ABORTIFHUNG, 1000, (DWORD*)&lRes);

      LPFNOBJECTFROMLRESULT pfObjectFromLresult = (LPFNOBJECTFROMLRESULT)::GetProcAddress(hInst, _T("ObjectFromLresult"));
      if(pfObjectFromLresult) {
        HRESULT hr = (*pfObjectFromLresult)(lRes, IID_IHTMLDocument, 0, (void**)&pHTMLDoc);
        if(SUCCEEDED(hr)) {
          CComPtr pSelObj;
          CComPtr pTxtRange;
          pHTMLDoc->get_selection(&pSelObj);
          hr = pSelObj->createRange((IDispatch**)&pTxtRange);
          if(FAILED(hr)) return hr;
          pTxtRange->select(); // 選取所有選中內容

          BSTR bstrTxt, bstrTxt1;
          pTxtRange->get_htmlText(&bstrTxt); // 取得選區 HTML
          char* strTxt = _com_util::ConvertBSTRToString(bstrTxt);

          // 取得主機域名 (用於相對路徑轉絕對)
          CComPtr pLocation;
          pHTMLDoc->get_location(&pLocation);
          pLocation->get_hostname(&bstrTxt1);
          char* strTxt1 = _com_util::ConvertBSTRToString(bstrTxt1);
          char strPath[MAX_PATH];
          sprintf(strPath, "https://%s", strTxt1);

          // 儲存檔案路徑
          char chSavePath[MAX_PATH];
          sprintf(chSavePath, "%s\\T%ld.htm", chFilePath, Number);
          CreateAllDirectory(chSavePath);

          // 處理圖片資源並修正連結
          std::string webpage;
          if(!CheckData(strTxt, strPath, chFilePath, Number, webpage))
            SaveCharToFile(strTxt, chSavePath, TRUE);
          else
            SaveCharToFile(webpage.c_str(), chSavePath, TRUE);

          reg.WriteDWORD("ArticleNumber", ++Number);

          // 清理
          delete[] strTxt1; delete[] strTxt;
          SysFreeString(bstrTxt1); SysFreeString(bstrTxt);
        }
      }
    }
    ::FreeLibrary(hInst);
  }
  CoUninitialize();
  return S_OK;
}

關鍵輔助函數

CheckData:解析 HTML、下載圖片、修正 src 路徑


BOOL CheckData(const LPCTSTR data, const LPCTSTR host, const LPCTSTR path,
               DWORD Number, std::string& outstring) {
  // 遞迴查找  標籤
  const char* p1 = FindString(data, "");
  if(!p1||!p2||!p3||!p4) return FALSE;

  // 複製標籤前段
  int n = p4 - p1 + 1;
  char chImgPath[1024];
  strncpy(chImgPath, p1, n);
  outstring.append(data, p2 - data + 5);

  // 提取圖片 URL
  n = p3 - p2 - 5;
  char chImgSrc[MAX_PATH];
  strncpy(chImgSrc, p2+5, n);

  char chDownLoadPath[MAX_PATH], chWriteImgSrc[MAX_PATH];
  char dirname[20]; ltoa(Number, dirname, 10);

  // 判斷相對/絕對路徑並構建下載/儲存路徑
  if(FindString(chImgSrc, "https://") == NULL) {
    if(FindString(chImgSrc, "..")) strcpy(chImgSrc, &chImgSrc[2]);
    sprintf(chDownLoadPath, "%s%s", host, chImgSrc);
    sprintf(chWriteImgSrc, "%s//%s%s", path, dirname, chImgSrc);
  } else {
    strcpy(chDownLoadPath, chImgSrc);
    const char* p5 = FindString(chImgSrc+7, "/");
    sprintf(chWriteImgSrc, "%s\\%s%s", path, dirname, &chImgSrc[p5-chImgSrc]);
  }

  // 建立目錄並下載
  CreateAllDirectory(chWriteImgSrc);
  HRESULT hr = URLDownloadToFile(NULL, chDownLoadPath, chWriteImgSrc, 0, NULL);
  if(SUCCEEDED(hr)) {
    const char* p6 = FindString(chDownLoadPath+7, "/");
    sprintf(chWriteImgSrc, "%s%s", dirname, &chDownLoadPath[p6-chDownLoadPath]);
  } else {
    strcpy(chWriteImgSrc, chDownLoadPath); // 下載失敗保留原路徑
  }

  // 路徑分隔符統一為反斜線
  for(int i=0; chWriteImgSrc[i]; i++)
    if(chWriteImgSrc[i]=='/') chWriteImgSrc[i]='\\';

  outstring.append(chWriteImgSrc);
  outstring.append(p3, p4-p3+1);

  // 遞迴處理剩餘內容
  if(!CheckData(p4+1, host, path, Number, outstring))
    outstring.append(p4+1);
  return TRUE;
}

其他工具函數

  • SaveCharToFile(data, filename, append):字串存檔 (覆寫/附加)。
  • FindString(source, key):字串查找 (封裝 strstr)。
  • CreateAllDirectory(path):遞迴建立多層目錄 (如 d:\a\b\c\d)。

技術難點與改進方向

  • 取得 Document 的笨方法:枚舉視窗 → WM_HTML_GETOBJECT → ObjectFromLresult。若知曉更優雅方式 (如直接從 external.menuArguments 取得) 請告知。
  • 相對路徑解析處理不夠完善 (如 ../、./、協議相對 //)。
  • 編碼轉換 (BSTR ↔ ANSI/UTF-8) 需更嚴謹處理。

總結

本方案實現「右鍵選取 → 自動儲存 HTML 與圖片資源到本地」,耗時一周完成。核心價值在於 IE 擴展入口 + ATL 組件 DOM 操作 + 資源下載修正 的完整鏈路。可延伸應用於離線閱讀、內容歸檔、素材采集等場景。

作者 Email: lgtest@sina.com,歡迎交流。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved