web 2.0爬虫類を編纂する——ページキャプチャ部分


web 2.0ページキャプチャ
web 2.0ページは何ですか?私の個人的な理解は死ぬページを書くのではないですか?
今日は皆さんに紹介するページキャプチャのフレームワークは、Googleが頭のないブラウザを操作するために出したnodejsベースのフレームです.このフレームのAPIは中国語のAPIドキュメントにあります.残念なことに、中国語のAPIは完全に翻訳されていないので、英語の文書を読む能力を高めなければなりません.
設置nodejs環境
ブログを参照してください
koaを利用してnodejsバックエンドサービスを構築する
このステップの役割は主に私たちが書いたページのスナップ部分を対外に直接に便利に呼び出すインターフェースを提供することです.koaを使うか、それとも他のNodejsのフレームワークを使うかは、ウェブサービスを使わなくても大丈夫です.
PppeteerとChromiumを設置する
nodejsの開発環境を作ったら、pppeteerとchromiumをインストールできます.
npm i puppeteer #      chromium,      
npm i puppeteer-core #     chromium,       
インストールできない場合はnpmとnodeの互換性がないため、npmとnodejsを再インストールするのが一番いいです.
簡単にWeb 2.0ページをつかむ
var glovakBrower;

async function crawl(url){
     
    if(typeof glovakBrower == 'undefined' || glovakBrower == 'undefine'){
     
        globalBrower = await puppeteer.launch({
     
            headless:true,
            args: [
                '--no-sandbox', 
                '--disable-setuid-sandbox',
                // '--proxy-server=http://127.0.0.1:18888'
            ],
            executablePath:'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
        });
    }

    var page = await glovakBrower.newPage();
    try {
     
        page.setDefaultNavigationTimeout(110 * 1000);
        page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.130 Safari/537.36');
        page.setJavaScriptEnabled(true);
        page.setCacheEnabled(false);
        page.setRequestInterception(true);
        page.on('request', interceptedRequest => {
     
            let url = interceptedRequest.url();
            if(url.indexOf('.png') > -1 || url.indexOf('.jpg') > -1 
                                        || url.indexOf('.gif') > -1 
                                        || url.indexOf('.pdf') > -1
                                        || url.indexOf('.txt') > -1
                                        || url.indexOf('.mp4') > -1
                                        || interceptedRequest.resourceType() === 'image')
              interceptedRequest.abort();
            else
              interceptedRequest.continue();
        });
        await page.goto(url);
        await page.waitForSelector('html'); 
        let html = await page.content();
        return html;
    } catch (error) {
     
        
    }
    finally {
     
        page.close();
    }
}
ここではgotoの方法だけを使って、waitForSelectorの方法でロードします.後でもっと合理的な方法を紹介します.これで、web 2.0ページの爬虫類を基本的につかむことができます.このような爬虫類は単にページのテキストを作るだけのサービスで十分です.
本物のweb 2.0ページキャプチャモジュール
1、DOMツリー遍歴2、イベント再帰トリガ3、フォーム自動記入など
上の機能はどうやって実現しますか?
まず、問題を見に来てください.
1、イベントがトリガされると、新しいイベントが発生し、同時にページが変化します.
この時、私たちはこのような問題を解決する二つの案があります.一つはページ全体を再読み込みすること、もう一つは画面の変化を監督することです.まず、前の方法で問題があります.ページを再読み込みすると、すべてのイベントが再起動され、混乱します.したがって、待ち受けページの変化を考慮して、DOMでDOM M 4型イベントを提供し、このイベントは一定期間ページのノード変化を保存し、非同期的にどのノードが変化したかを通知することができる.私達は変化を得た後、domツリーを巡回する方式、例えばtree Walkerなどが新たなDOMノードを巡回して、イベント識別とトリガを行うだけでいいです.存在する問題:非同期なので、上層部が触発した時に、下層部のイベントがどれぐらい実行されるかを判断できない.同期方式ではなく、下層部の触発が終わったら上層部に戻るイベントの触発を待つしかない.構想を最適化します:nodejs自身もシングルスレッドですので、実行はきっと前後関係があります.
2、一部のイベントをトリガする場合、Windows.openとwindow.close操作を呼び起こす可能性があります.
ブラウザーのwindow.openとwindow.closeの方法のデフォルトの行為を修正して、ページのリダイレクトの行為が現れないようにします.なぜ避けますか?前に述べましたが、openであれ、locationであれ、ページの本体が変化し、ページが再読み込みされ、すべてのイベントが再起動され、混乱します.pppeteerは、関連する方法を提供しています.ページの読み込み前にBOMのオブジェクトのデフォルトの挙動を修正しますが、すべてのオブジェクトのすべての方法が変更されるわけではなく、ブラウザがどのようにこれらの方法を定義するかによって異なります.幸い、window.openとwindow.closeの方法は完全に修正できます.
3、一部のイベントをトリガする場合、Windows.locationの操作を呼び起こす可能性があります.
window.locationメソッドはブラウザでデフォルトでは変更できませんので、コードの中で直接にデフォルトの行動を修正すると効果がありません.解決案:chromiumのソースコードを修正して、chromeを再コンパイルします.
4、フォームに記入する場合、提出をクリックするとページが再読み込みされます.
まずsubmitメソッドのデフォルトの挙動を修正し、フォームにdom 2型submitイベントを追加し、dispatch Eventを利用してイベントをトリガします.
コード部分
ブラウザオブジェクトを作成するには、ndoejsはデフォルトでは単一スレッドですので、ブラウザオブジェクトを全体に広げておくだけでメモリの無駄を減らすことができます.
if (typeof globalBrower === 'undefined' || globalBrower === 'undefine') {
     
        globalBrower = await puppeteer.launch({
     
            headless: false,
            ignoreHTTPSErrors: true,        //       
            waitUntil: 'networkidle2',
            defaultViewport: {
     
                width: 1920,
                height: 1080
            },
            args: [
                '--disable-gpu', //   GPU
                '--disable-dev-shm-usage',
                '--disable-web-security',
                '--disable-xss-auditor',    //    XSS Auditor
                '--no-zygote',
                '--no-sandbox', //       
                '--disable-setuid-sandbox',
                '--allow-running-insecure-content',     //        
                '--disable-webgl',
                '--disable-popup-blocking',
            ],
            executablePath: 'path/to/chrome',
        });
    }
先ほどお話ししましたが、ページを読み込む前に、ブラウザのデフォルトの行動を変更する必要があります.page.evaluate OnNewDcumentの方法が使えます.
//   location     
//   :     ,              Location      
await page.evaluateOnNewDocument(() => {
     

            var oldLocation = window.location;
            var fakeLocation = Object();
            fakeLocation.replace = fakeLocation.assign = function (value) {
     
            };
            fakeLocation.reload = function () {
     
            };
            fakeLocation.toString = function () {
     
            };
            Object.defineProperties(fakeLocation, {
     
                'href': {
     
                    'get': function () {
     
                    },
                    'set': function (value) {
     
                    }
                },
                // hash, host, hostname ...
            });
            var replaceLocation = function (obj) {
     
                Object.defineProperty(obj, 'location', {
     
                    'get': function () {
     
                    },
                    'set': function (value) {
     
                    }
                });
            };

            replaceLocation(window);
            addEventListener('DOMContentLoaded', function () {
     
            })
        });
//   bom            
async function initHook() {
     

    window.onbeforeunload = function (e) {
     
    };


    //todo hook History API,          API      ,  url     
    window.history.pushState = function (a, b, url) {
     
    };
    window.history.replaceState = function (a, b, url) {
     
       
    };
    Object.defineProperty(window.history, "pushState", {
     "writable": false, "configurable": false});
    Object.defineProperty(window.history, "replaceState", {
     "writable": false, "configurable": false});

    // todo   hash  ,Vue       hash          
    window.addEventListener("hashchange", function () {
     
       
    });

    // todo           ,        url,       
    window.alert = () => {
     
    };
    Object.defineProperty(window, "alert", {
     "writable": false, "configurable": false});

    window.prompt = (msg, input) => {
     
    };
    Object.defineProperty(window, "prompt", {
     "writable": false, "configurable": false});

    window.confirm = () => {
     
    };
    Object.defineProperty(window, "confirm", {
     "writable": false, "configurable": false});

    window.open = function (url) {
     
    };
    Object.defineProperty(window, "open", {
     "writable": false, "configurable": false});

    window.close = function () {
     
    };
    Object.defineProperty(window, "close", {
     "writable": false, "configurable": false});

    window.__originalSetTimeout = window.setTimeout;
    window.setTimeout = function () {
     
    };
    window.__originalSetInterval = window.setInterval;
    window.setInterval = function () {
     
    };

    XMLHttpRequest.prototype.__originalOpen = XMLHttpRequest.prototype.open;
    XMLHttpRequest.prototype.open = function (method, url, async, user, password) {
     
    }
    XMLHttpRequest.prototype.__originalSend = XMLHttpRequest.prototype.send;
    XMLHttpRequest.prototype.send = function (data) {
     
    }

    HTMLFormElement.prototype.__originalSubmit = HTMLFormElement.prototype.submit;
    HTMLFormElement.prototype.submit = function () {
     
    	// hook code
    }

}
対象を終了すると、ページイベントがトリガされた時に、私達はPage.on方法を使用して処理する必要があります.
await page.on('request', async interceptedRequest => {
     
});

await page.on('dialog', async dialog => {
     
});

await page.on('response', interceptedResponse => {
     
});

await page.on('requestfailed', async failed => {
     
});

await page.on('console', async msg => {
     
});

page.on('framenavigated', frameTo => {
     
});
なお、requestイベントをトリガするには、ページブロッキングを開く必要があります.page基礎設定に関するコードは
await page.setDefaultNavigationTimeout(110 * 1000);
await page.setUserAgent('Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko)Chrome/44.0.2403.130 Safari/537.36');
await page.setJavaScriptEnabled(true);
await page.setCacheEnabled(false);
await page.setRequestInterception(true); //    request        
await page.setViewport({
     "width": 1920, "height": 1080});
以上の準備が完了したら、gotoメソッドを呼び出してページをロードすることができます.コールの先着順に注意してください.でないと、思わぬ問題が発生します.
await page.goto(url, {
     
   waitUntil: 'networkidle2'
});
その中で、wait Unitilの存在はgotoメソッドが非同期であるため、networkdle 2(2つのネットワーク通信)があるまで待つことになる.そして、私たちはbodyのノード全体の変化を監視し、dom 4レベルのイベントモニターを利用すればいいです.
await page.$eval('body', () => {
     
            // todo       dom4  , dom    ,         
            // mutations    node_list
            let observer = new MutationObserver((mutations) => {
     
                console.log('eventLoop-nodesMutated:', mutations.length, typeof mutations);
                mutations.forEach((mutation) => {
     
                    if (mutation.type === 'childList') {
     
                        for (let i = 0; i < mutation.addedNodes.length; i++) {
     
                            //   treeWalker      
                        }
                    } else if (mutation.type === 'attributes') {
     
                    }
                });
            });
            observer.observe(document.getElementsByTagName('body')[0], {
     
                childList: true,
                attributes: true,
                characterData: false,
                subtree: true,
                characterDataOldValue: false,
                attributeFilter: ['src', 'href'],
            });
        });
最後に、すべてのフォームを巡回してイベントを作成し、トリガすればいいです.
let formsParams = await page.evaluate(() => {
     
    //     
    let regexps = {
     
    };
    let name_type = {
     
    };
    let name_values = {
     
    };
    let textLengthReg = '([\\s\\S]*)?((len$)|(length$)|(size$))';
    let formsParams = [];
    for (let i = 0; i < document.forms.length; i++) {
     
        let form = document.forms[i];
        // console.log(form.method, form.action);
        let ans = {
     };
        // todo form can't intercept, so I
        for (let j = 0; j < form.length; j++) {
     
            let length = -1;
            let input = form[j];
            let name = input.name.replace('_', '').toLowerCase().trim();
            if (input.type === 'submit') {
     
                //          submit,     
                form.addEventListener('submit', (e) => {
     
                    e.preventDefault();
                    new FormData(form);
                });
                form.addEventListener('formdata', (e) => {
     
                    let data = e.formData;
                    let request = new XMLHttpRequest();
                    request.open(form.method, form.action);
                    request.send(data);
                });

                let evt = document.createEvent('HTMLEvents');
                evt.initEvent('submit', true, true);
                try {
     
                    form.dispatchEvent(evt);
                } catch {
     
                }
                continue;
            }
            for (let key in regexps) {
     
                if (name.match(regexps[key])) {
     
                    //   name           ,     
                    
                    break;
                }
            }
            formsParams.push({
     
                'url': form.action,
                'method': form.method,
                'params': ans
            });
        }
    }
    return formsParams;
});
あとはタグのトリガが簡単です.自分で解決できます.