SaaSアプリの構築:基礎を越えて


これは、あなた自身のSaaSアプリケーションを構築するシリーズの最初のポストです.我々は、実際の製品を構築するために必要なものを介して一歩ずつ行く:支払い、システムの監視、ユーザー管理、および多くを取る.
では、どのような製品を作るのですか?
我々は完全に機能する(最小なら)Googleランクトラッカーを構築するつもりです.
ドメイン、いくつかのキーワードを入力し、アプリは時間をかけてGoogle検索でパフォーマンスを追跡します.この考えはビジネスセンスを作るか?たぶんない!しかし、それは何か便利なことをする楽しいアイデアです、それは我々が達成することができるタスクです、そしてあなたが好きな限りそれを取ることができます.我々は道に沿ってSaaSアプリを構築するすべての基本をカバーします.
You can find the complete code on GitHub.

目次






















  • Google検索スクレーパーの構築


    Google検索結果をこすってこのアプリケーションのコアです.私たちはどこからでも建築を始めることができましたが、スクレイパーそのものから始めても意味があると思います.
    スクレーパーは検索クエリを取り、結果のいくつかのページをロードする必要があります.スクレーパーは、我々のアプリにこれらの結果を返します.それはとても簡単ですね!しかし、多くの間に間違って行くことができます.我々は不幸な顧客からのirateメールを必要としないので、コードの多くは失敗を処理するために専用されます.

    AWSインスタンスにおける操り人形の設定


    使いましょうPuppeteer 掻き取りをする.Puppeteerは、リモートのクロムブラウザのセッションを制御するためのJavaScript APIを提供します.すべてのベスト、ブラウザはデスクトップ環境(ヘッドレスモード)なしで実行することができますので、我々のコードは、クラウド内のサーバー上で独立して実行することができます.このチュートリアルでは、AWS上の18.04インスタンスを開始し、Pitpeteerに必要な依存関係のすべてをインストールします.
    EC 2を使っていますtc2.medium このプロジェクトのインスタンス.これは2つのvcpusと4 GBのRAMが付属していますので、私たちが後に加えるつもりであるだけでなく、操り人形を動かすのに十分強力です.Ubuntu 18.04インスタンスは良い出発点です.
    Chromeには、Petpeteerにバンドルされていますが、私たちが始める前に必要とされる、必須のシステムライブラリの広い配列があります.幸いにも、我々はこの1つのライナーでインストールされたすべてを得ることができます.
    sudo apt-get install -y ca-certificates fonts-liberation libappindicator3-1 libasound2 libatk-bridge2.0-0 libatk1.0-0 libc6 libcairo2 libcups2 libdbus-1-3 libexpat1 libfontconfig1 libgbm1 libgcc1 libglib2.0-0 libgtk-3-0 libnspr4 libnss3 libpango-1.0-0 libpangocairo-1.0-0 libstdc++6 libx11-6 libx11-xcb1 libxcb1 libxcomposite1 libxcursor1 libxdamage1 libxext6 libxfixes3 libxi6 libxrandr2 libxrender1 libxss1 libxtst6 lsb-release wget xdg-utils
    
    クロム依存性がインストールされると、ノードV 14の設定に移行することができる.これを行う最も簡単な方法は、ダウンロード可能なsetupスクリプトを介して行われます.これは、パッケージマネージャに、ノードのv 14を見つける方法を教えます.
    curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
    bash nodesource_setup.sh
    apt-get install -y nodejs
    
    この時点で、ノードとクロムがインストールされます.次にパッケージを作ります.JSONファイルのようにNPMを使用してプロジェクトの依存関係をインストールすることができます.
    {
        "name": "agent-function",
        "version": "0.0.1",
        "dependencies": {
            "axios": "^0.19.2", // For communicating with the app server.
            "puppeteer": "10.0.0",
            "puppeteer-extra": "3.1.8",
            "puppeteer-extra-plugin-stealth": "2.7.8"
        }
    }
    
    アフターランニングnpm install , あなたは場所に必要なすべての必要があります.非常に簡単なノードスクリプトを使用して、操り人形がインストールされ、動作することを確認しましょう.
    const puppeteer = require("puppeteer-extra");
    
    async function crawl() {
        console.log("It worked!!!");
    }
    
    puppeteer
        .launch({
            headless: true,
            executablePath:
                "./node_modules/puppeteer/.local-chromium/linux-884014/chrome-linux/chrome",
            ignoreHTTPSErrors: true,
            args: [
                "--start-fullscreen",
                "--no-sandbox",
                "--disable-setuid-sandbox"
            ]
        })
        .then(crawl)
        .catch(error => {
            console.error(error);
            process.exit();
        });
    
    Configオブジェクトのヘッドホンキーに注意してください.これは、chromeがEC 2でサーバー上で動作するときに必要とするGUIなしで起動することを意味します.うまくいけば、すべてうまくいけば、あなたが表示されますIt worked!!! このスクリプトを実行するとコンソールに印刷されます.

    簡単なGoogle検索要求をすること


    我々はすべてが正しくインストールされていることを知っているので、我々は簡単なGoogle検索を行うことから始める必要があります.我々は、この時点で実際の掻き取りを気にしないでしょう.目標は単に検索バーに検索クエリを入力するには、Googleの結果をロードし、それが動作していることを証明するスクリーンショットを取ることです.
    これは、私がちょうど説明したことをするためにそれを更新した後のクロール機能です.
    async function crawl(browser) {
        const page = await browser.newPage();
        await page.goto("https://www.google.com/?hl=en");
    
        // Find an input with the name 'q' and type the search query into it, while 
        // pausing 100ms between keystrokes.
        const inputHandle = await page.waitForXPath("//input[@name = 'q']");
        await inputHandle.type("puppeteer", { delay: 100 });
    
        await page.keyboard.press("Enter");
        await page.waitForNavigation();
    
        await page.screenshot({ path: "./screenshot.png" });
        await browser.close();
    }
    
    PuppeteerはGoogle検索ページを読み込みますhl=en 英語版をリクエストするには、検索クエリーに入り、Enterキーを押します.
    The waitForNavigation メソッドは、ブラウザがロードイベントを発行するまで、スクリプトを一時停止します(つまり、ページやCSSや画像などのすべてのリソースが読み込まれます).これは重要です.なぜなら、スクリーンショットを撮る前に結果が見えるまで待ちましょう.

    閉じるこの動画はお気に入りから削除されていますscreenshot.png スクリプトの実行後.

    スクレーパー要求のためのプロキシネットワークの使用


    しかし、たとえあなたの最初の要求が成功したとしても、結局、あなたはCAPTCHAに直面するでしょう.あなたが同じIPアドレスからあまりに多くの要求を送るならば、これはかなり不可避です.

    解決策は、プロキシのネットワークを介してルートリクエストをキャプチャするcaptchaブロックをトリガすることです.スクレーパーは、常に時間がブロックされますが、任意の運で、我々の要求の大半は、それを介して行われます.
    プロキシの多くの異なる種類、およびベンダーのオプションの膨大な数があります.このようなスクレーピングプロジェクトには主に3つのオプションがあります.
  • ProxyAllのようなサービスを通して、IPアドレスまたはIPアドレスの束を購入すること.これは最低価格のオプションです.私は約5ドル/月の5つのIPアドレスを購入しました.
  • IPアドレスの広い範囲を提供するが、帯域幅のために課金するデータセンタープロキシ.SmartProxyは、例として、100 GBを100ドルに提供します.しかしながら、これらのIPアドレスの多くは既にブロックされています.
  • 住居のプロキシも広範囲にわたるIPアドレスを提供します、しかし、アドレスは居住者またはモバイルISPから来ます、そして、したがって、より頻繁にcaptchaに遭遇します.トレードオフは価格になる.SmartProxyは5 GBのデータ転送のために75ドルを請求します.
  • あなたのスクラップは非常にゆっくり動作し、頻繁に要求を行う場合は、プロキシなしで取得することができます.私は実際に自分のサイトのランキングを追跡するため、専用のIPアドレスの一握りで行くことが意味した.
    プロキシの上に要求を送信すると、デフォルトのネットワークの代わりに、Pitpeteerと簡単です.スタートアップargsリストはproxy-server 値.
    puppeteer
        .launch({
            headless: false,
            executablePath:
                "./node_modules/puppeteer/.local-chromium/linux-884014/chrome-linux/chrome",
            ignoreHTTPSErrors: true,
            args: [
                `--proxy-server=${proxyUrl}`, // Specifying a proxy URL.
                "--start-fullscreen",
                "--no-sandbox",
                "--disable-setuid-sandbox"
            ]
        })
    
    The proxyUrl かもしれないhttp://gate.dc.smartproxy.com:20000 . ほとんどのプロキシ構成は、認証メソッドとしてIPホワイトリストを使用していない限り、ユーザ名とパスワードを必要とします.任意の要求を行う前に、そのユーザー名/パスワードの組み合わせを認証する必要があります.
    async function crawl(browser) {
        const page = await browser.newPage();
        await page.authenticate({ username, password });
        await page.goto("https://www.google.com/?hl=en");
    }
    
    どんな重い使用されたスクレイパーもまだブロックされることを経験しています、しかし、きちんとしたプロキシは我々が良いエラー処理を構築する限り、プロセスを持続可能にします.

    検索結果の収集


    我々は現在、プロセスの実際の掻き取り部分になります.アプリの全体的な目標は、ランキングを追跡することですが、シンプルさのために、スクレイパーは、特定のウェブサイトやドメインを気にしない.代わりに、スクレイパーは単にリンクのリストを返します(ページで見られる順序で!)アプリケーションサーバーに.
    これを行うには、XPathに依存してページ上の正しい要素を選択します.CSSセレクタは、複雑な掻き取りのシナリオになると、あまり良くない.この場合、Googleは簡単なIDやクラス名を提供しません.我々は、クラス名の組み合わせだけでなく、タグの構造、リンクの正しいセットを抽出するに依存する必要があります.
    このコードは、リンクを抽出し、次のボタンを所定の回数を押すか、または次のボタンがないまで.
    let rankData = [];
    while (pages) {
        // Find the search result links -- they are children of div elements
        // that have a class of 'g', while the links themselves must also
        // have an H3 tag as a child.
        const results = await page.$x("//div[@class = 'g']//a[h3]");
    
        // Extract the links from the tags using a call to 'evaluate', which
        // will execute the function in the context of the browser (i.e. not
        // within the current Node process).
        const links = await page.evaluate(
            (...results) => results.map(link => link.href),
            ...results
        );
    
        const [next] = await page.$x(
            "//div[@role = 'navigation']//a[descendant::span[contains(text(), 'Next')]]"
        );
    
        rankData = rankData.concat(links);
    
        if (!next) {
            break;
        }
    
        await next.click();
        await page.waitForNavigation();
    
        pages--;
    }
    
    今、我々は検索結果を持っている、どのように我々はノードのプロセスから取得し、どこかに記録されるか?
    これを行うには多くの方法がありますが、私はアプリがポストスクレイパーのために利用可能なAPIを作るように選択したので、ポストリクエストとして結果を送信することができます.Axiosライブラリは、これはかなり簡単になりますので、私はここでどのように見えるかを共有します.
        axios
            .post(`http://172.17.0.1/api/keywords/${keywordID}/callback/`, {
                secret_key: secretKey,
                proxy_id: proxyID,
                results: rankData,
                blocked: blocked,
                error: ""
            })
            .then(() => {
                console.log("Successfully returned ranking data.");
            });
    
    心配するなblocked or error ここの変数.すぐにエラー処理に入ります.ここで最も重要なことはrankData 変数は、すべての検索結果のリンクを含むリストを参照します.

    スクレーパエラー処理


    予想外の処理はどんな種類のプログラミングでも重要です、しかし、特にスクレーパーで.が間違って行くことができる多くがあります:キャプチャ、プロキシ接続の失敗、我々のXPathは時代遅れになる、一般的なネットワークのflakiness、および詳細に実行します.
    我々のエラー処理のいくつかは後で来ます.なぜなら、私たちはスクレーパー・コードそのものだけでしかできないからです.アプリは、それが再試行する必要があるときに知っているスマートにする必要がありますまたはそれがあまりに頻繁にブロックされているため、特定のプロキシのIPアドレスを引退する必要がある場合.
    あなたが以前から思い出すならば、スクレイパーはAを返しますblocked 値.スクレーパーがブロックされているかどうかを確認しましょう.
        let blocked = false;
    
        try {
            const [captcha] = await page.$x("//form[@id = 'captcha-form']");
            if (captcha) {
                console.log("Agent encountered a CAPTCHA");
                blocked = true;
            }
        } catch (e) {}
    
    このコードは単にIDを持つフォームの存在を探すcaptcha-form を設定し、blocked もしそうならばtrueを返します.我々は後で表示されるように、プロキシのIPがあまりにも多くの回ブロックとして報告されている場合、アプリケーションはもはやそのIPアドレスを使用します.

    次は何ですか。


    私はあなたがSaaSアプリシリーズのこの最初の部分を楽しんだことを望む!次に、私たちのスクレーパーが呼び出すAPIを持っているので、NGINX、フラスコ、およびPostgresを使って設定します.あなたは常に見つけることができますcomplete code for the project on GitHub.