使用nodejs刮擦工作
#javascript #node #scraping

SEE SOURCE CODE

目录

  1. Why js
  2. Doing it the simple way
  3. Cloudflare waiting room
  4. how to bypass cloudflare waiting room
  5. Chosing your end-point

为什么要JavaScript?

您的业务无关!

这样做的简单方式

我喜欢简单!谁不对?因此,解决问题的一种方法是:

  1. 使用内置的HTTPS模块直接提出请求到预定的URL
  2. 随着数据出现在块中,我们需要使用流来收集数据,一旦收集在HTML文件中
  3. 解析东西。
  4. 发送本地通知或API请求或Godamn RPC或您想用新工作提醒的任何人

看起来像这样:

const fs = require('fs');
const http = require('https');
const url = 'https://www.upwork.com/ab/jobs/search/?q=javascript&sort=recency'
http.get(url, (res) => {
    let data = '';
    res.on('data', (chunk) => {
        data += chunk;
    });
    res.on('end', () => {
    // save it as html file
        fs.writeFile('upwork.html', data, (err) => {
            if (err) throw err;
            // if a match (your jobs keyword matches ) found send a notification
            if (parse(data)) {
                sendNotification();
            }   
        });
    });
}).on("error", (err) => {
    // handle error 
});

嗯!不太快!!!在步骤3中,Cloudflare!

cloudflare waiting room

Cloudflare等候室

UPWORK是CloudFlare客户端,CloudFlare不喜欢您注意到的机器人!如果您不熟悉CloudFlare,它是世界上最大的CDN提供商之一,或者至少是他们最著名的,但它们还提供了许多其他服务,例如负载平衡,防火墙等...现在阻止我们的服务是WAF(Web应用程序防火墙)防火墙,该防火墙可保护Web应用程序免受坏东西(DDOS攻击,跨站点脚本)的影响,并提供有用的功能,例如性能优化,缓存控制等。 bot management是其中之一。机器人如果无法控制,可能会通过消耗资源并可能造成拒绝服务攻击而对Web属性造成很大的损害。因此,CloudFlare使用行为分析和机器学习来管理这些矩阵生物。可是等等 !!那好机器人呢?搜索引擎爬网(Google,Bing),性能监控工具和其他网络正常运行所需的东西!云弹力通过将它们列入白色列表并将不良的机器人列出来区分好机器人和坏机器人。您和我想知道的是不好的机器人!

如何绕过Cloudflare等候室

我们应该看起来像真正的人类。我们需要我们的要求看起来像是来自真正的浏览器中的真实人! puppeteer进行营救!!

Puppeteer是一个节点库,它提供了高级API,可以控制DevTools协议上的Chrome或Chromium。它也可以配置为使用完整的(无头)铬或铬。问题是您可以控制一个无头浏览器,并使其完成各种人类可能会做的事情,例如单击链接,滚动页面并填写表格。而且,如果Cloudflare或任何机器人管理器都给您带来困难,并且认为自己是机器人,则可以通过注入延迟和鼠标运动来使用Puppeteer为脚本添加一些随机性,从而使浏览器看起来不那么机器人。例如,您可以使用Puppeteer在单击链接之前随机暂停浏览器暂停,或者在填写表单之前以随机模式移动鼠标。这将使Cloudflare更难检测您使用机器人。

因此,我们要用木偶撒谎来骗子,并告诉它我们是一个真正的人,而不是机器人。

installing puppeteer之后,让我们把它奏效:

const puppeteer = require('puppeteer');
const fs = require('fs');
(async () => {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();

    await page.setUserAgent('Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36');
    await page.goto('https://www.upwork.com/ab/jobs/search/?q=javascript&sort=recency');
    await page.waitForTimeout(1000);
    // wait for the page to load
    const html = await page.content();
    fs.writeFile('upwork.html', html, (err) => {
        if (err) throw err;
        console.log('The file has been saved!');
    });
    await browser.close();
})();

我们的代码实际上没有太大变化:

  • 第3行:我们定义了一个将执行我们逻辑的异步函数(why?)。

  • 第4行:然后,我们使用puppeteer.launch()方法创建一个无头浏览器的新实例。

  • 第5行:然后,我们使用browser.newPage()方法在浏览器中创建一个新页面,该方法将用于导航到UPWork网站并刮擦工作列表。

这是重要部分:

  • 第7行:我们使用page.setUserAgent()方法设置了页面的用户代理,以使浏览器看起来是真正的Web浏览器,而不是BOT。

用户代理是由Web浏览器发送的文本字符串,以识别自身及其功能。服务器使用此信息来确定用于浏览器的内容和功能,并跟踪来自不同浏览器的流量。在此代码中,使用page.setuseragent()方法设置了用户代理。此方法将字符串作为参数,该参数指定将发送到服务器的用户代理。在这种情况下,将用户代理设置为将浏览器识别为Linux操作系统上Google Chrome的最新版本的字符串。这样做是为了使无头浏览器似乎是一个真正的网络浏览器,而不是机器人,这可能有助于绕过网站已有的任何反机器人措施。

  • 第8..9行:代码使用page.goto()方法导航到UPWork网站,并等待使用page.waitForTimeout()方法加载页面。模仿真正的人类行为。 (不是那么人类..但仍然如此)。

  • 休息.. :一旦加载页面后,我们使用page.content()方法获取页面的HTML内容,然后使用fs.writeFile()方法将HTML写入文件。最后,我们使用browser.close()方法关闭浏览器。这将结束脚本并阻止无头浏览器运行。

cloudflare bot detection

您看到的是我们从URL响应的新的HTML页面。实际上,我们成功绕过了Cloudflare等候室。您可以注意到,我们获得了工作清单页面,然后立即获得404页。因此,除了Cloudflare候诊室外,UpWorks似乎还有其他机器人检测机制。这些家伙真的很认真。嗯!我认为我们也应该更加认真。

在这里和那里进行了一些修补之后,我想出了以下脚本:

(async () => {
    // reading keywords from keywords.txt file
    let keywords = fs.readFileSync('/Users/wa5ina/Porn/automation/upwork-bot/keywords.txt', 'utf-8');
    keywords = keywords.split('\n');
    for (let i = 0; i < keywords.length; i++) {
        keywords[i] = keywords[i].trim();
    }
    // Launch the browser in non-headless mode
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    // Set a realistic user agent string
    await page.setUserAgent('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36');

    // set the viewport to 1920x1080 to avoid the cookie banner 
    await page.setViewport({
        width: 1920,
        height: 1080
    });
    await page.goto('https://www.upwork.com/ab/account-security/login');
    // Wait for the page to load 
    await page.waitForTimeout(1000);

    // getting the email and password from the .env file
    const email = process.env.EMAIL;
    const password = process.env.PASSWORD;

    // enter the email and password
    await page.type('#login_username', email);
    // click the "continue with email" button
    await page.click('#login_password_continue');
    // some randomness to the mouse movement
    for (let i = 0; i < 10; i++) {
        await page.mouse.move(getRndm(0, 10000), getRndm(0, 1000));
        await page.waitForTimeout(1000);
    }
    // password
    await page.type('#login_password', password);
    await page.click('#login_control_continue');
    // move the mouse randomly to be more human 🤡
    for (let i = 0; i < 10; i++) {
        await page.mouse.move(getRndm(0, 20000), getRndm(0, 10000));
        await page.waitForTimeout(1000);
    }
    // wait for the page to load
    // wait for the search input to load 
    let allJobs = [];

    // wait for search input to load
    // await page.waitForSelector('input[placeholder="Search for job"]', { visible: true });
    for (let i = 0; i < keywords.length; i++) {
        // console.log('searching for ' + keywords[i]);
        for (let j = 0; j < 5; j++) {
            // scrolling throught 5 pages 
            await page.goto('https://www.upwork.com/ab/jobs/search/?q=' + keywords[i] + '&page=' + j + '&sort=recency');
            await page.waitForTimeout(3000);
            await page.waitForSelector('div[data-test="main-tabs-index"]', { visible: true });
            // get all sections with data-test="JobTile"
            const listings = await page.$$('section[data-test="JobTile"]');
            // change the page number of jobs
            let jobs = await Promise.all(listings.map(async (listing) => {
                // get the title of the job which in <h4 class="job-tile-title"> <a> </a> </h4>
                let posted = await getTime(listing);
                // if it's too old, then skip it
                if (tooOld(posted) === true)
                    return;
                // get title of the job
                let title = await getTitle(listing);
                // get the link of the job 
                let link = await getLink(listing);
                // get the description of the job 
                let description = await getDescription(listing);
                // get type of job {type, budget}
                let typeOfJob = await getTypeOfJob(listing);
                if (tooCheap(typeOfJob) === true)
                    return;
                // // is client's payment verified (true or false)
                let paymentverified = await isVerified(listing);
                return { posted, title, link, description, typeOfJob, paymentverified };
            }
            ));
            // filter out the undefined jobs
            jobs = jobs.filter((job) => job !== undefined);
            // push jobs to alljobs
            allJobs.push(...jobs);
        }

    }
    // Add some randomness to the requests
    const randomDelay = Math.random() * 2000;
    await page.waitForTimeout(randomDelay);
    // Close the browser
    await browser.close();
    // write to json file by overriding the file
    fs.writeFileSync('/Users/wa5ina/Porn/automation/upwork-bot/jobs.json', JSON.stringify(allJobs, null, 2));
})();


它首先是在名为keywords.txt的文件列表中读取的关键字列表。这些是我们的魔术词(由line feed'\ n'隔开),代码用于搜索作业。

接下来,代码启动其可信赖的Web浏览器并创建一个新页面。它将用户代理字符串设置为假装是真正的Web浏览器,因为它太酷了,无法成为机器人。它还将视口设置为1920x1080,以避免烦人的饼干横幅。

代码然后转到UPWORK登录页面,并耐心地等待加载。它从.env文件中获取您的电子邮件和密码,并将其输入登录页面上的适当字段。 (我之所以选择在刮擦前登录的原因是因为我注意到身份验证和非身份验证的工作质量明显不同),然后单击“继续”按钮以继续登录过程。

为了使事情变得更有趣,代码将鼠标随机移动以模仿人类用户。页面加载时的时间就像是有点舞。 tooo smart haaaaaðü!!并不真地 !!!行。

加载页面后,代码将导航到UPWork求职页面,并开始使用关键字数组中的每个关键字搜索作业。这就像为您的梦想项目寻找寻宝!对于每个关键字,它最多循环搜索结果。

对于搜索结果页面上列出的每个作业,代码提取作业标题,链接,描述和类型(例如,小时或固定价格)和预算。它将所有这些多汁的信息存储在名为allJobs的阵列中。

搜索了所有关键字并删除了所有作业后,代码关闭Web浏览器将事物记录到jobs.json文件中。

(请注意,我没有明确地为提取getTitle()getLink()等数据的某些特定功能添加代码,如果我这样做的话,我们可能会最终获得一篇丑陋的不可读的博客文章,假设它尚未,但是您可以找到github repo222222的整个代码

但我将简要解释tooOld()tooCheap()函数。因为它们有点重要。我决定过滤掉太老或太便宜的工作。
太老意味着这项工作是在20分钟前发布的。而且太便宜意味着如果固定价格或小时小于15美元,这项工作小于500美元。如果您打算使用脚本,则可以编辑这些功能,以满足您太便宜或太旧的。

ffter运行脚本,我们将获得一个看起来像这样的jobs.json文件:

[
  {
    "posted": "5 minutes ago",
    "title": "Senior Software and App Engineer",
    "link": "https://www.upwork.com/jobs/Senior-Software-and-App-Engineer_~012d16e9ca1001988c/",
    "description": "We need an absolute ninja to go through and clean up our entire platform, and our mobile apps to perform at their highest levels possible to increase our satisfaction and functionality in the field. We need a perfectionist, and someone who works efficiently because of their superior abilities. We have several integrations that need fine-tuned and more in the pipeline that will need done. So intimate knowledge of Api and SDK integrations will be necessary. He will also assist an IT support and be available for emergency service in the case of complete failure. We do not see this being the case as we are hiring you to make the system fail proof. We are young growing, high definition, video intercom system, and there is long-term potential be on this contract term.",
    "typeOfJob": {
      "type": "Hourly: ",
      "budget": "$35.00-$46.00"
    },
    "paymentverified": true
  },
  {
    "posted": "7 minutes ago",
    "title": "Build a web and mobile application",
    "link": "https://www.upwork.com/jobs/Build-span-web-span-and-mobile-application_~015ad09536ea065d5d/",
    "description": "You can read the specification document attached. This document contains all what you need.",
    "typeOfJob": {
      "type": "Fixed-price",
      "budget": "$1000 "
    },
    "paymentverified": false
  },
  {
    "posted": "7 minutes ago",
    "title": "Order platform",
    "link": "https://www.upwork.com/jobs/Order-platform_~0185808b1d24763136/",
    "description": "Looking to get a orders platform for a remittance company. Using Laravel Customer must be able to sign up, login, get live rate, book transaction and manage beneficiary and linked attributes. What would be the time line and approx cost",
    "typeOfJob": {
      "type": "Hourly",
      "budget": "not specified"
    },
    "paymentverified": true
  },
 ]

简单的对象数组。每个对象代表一个工作。现在我们有了我们的列表,我们可以随心所欲地使用Discord.js将其发送到Discord频道,或者使用节点邮件将其发送到您的电子邮件中,或者我不知道?将其发送给您的奶奶心灵感应ðµ,选择是您的。
我选择将其显示为我可爱的Mac上的通知。

选择您的终点

如果您想将作业显示为MACOS通知,请跟进此部分

它的代码非常简单,它只是读取jobs.json文件并显示每个作业的通知。

我使用了一些不太喜欢的实用程序,称为jq来解析JSON并提取有关工作的所有多汁详细信息,例如标题,发布日期,类型,预算和链接。

然后,我简单地浏览了每项工作,并为每个工作摘下各个细节。我使用了另一个名为alerter的实用程序,它只是osascript的包装器,osascript被搞砸了,因此拧紧,您需要一些疯狂的魔法才能正确解释您的壳变量,Alerter提供了说“他妈的和报价和报价和报价”的便利。反闪烁”。
因此,对于每项工作,我都会使用工作详细信息来调用Alerter,并将显示带有作业标题,发布日期,类型,预算和链接的通知。如果我感到冒险,我可以单击“打开”按钮以查看浏览器中的作业链接。

通知看起来像这样:

upwork macos notification

,如果您喜欢报价,则可以单击“打开”按钮以打开浏览器中的作业链接。

upwork macos notification

再次,该帖子丑陋且无组织,因此我不会包含此部分的代码,但是您可以再次在github repo中找到它。

现在,我们拥有机器人和通知脚本所有设置,现在该自动化流程了,以便我们可以坐下来让机器人为我们完成所有工作。为此,我们将使用称为cronjobs的东西。这些就像在预定时间运行脚本的小机器人。这就像为您的计算机雇用私人助理!

首先,让我们添加bot.js作为一个cronjob。打开终端并输入crontab -e。这将打开crontab编辑器。因此,由于我们的脚本将刮擦UPWORK网站并填写Jobs.json文件,因此我将每7分钟运行一次,以确保我获得最新的工作。因此,我将在crontab文件中添加以下行:

*/7 * * * * node /path/to/bot.js

这告诉Cronjob每7分钟运行BOT.JS脚本。您可以将时间表调整到您的喜好 - 查看此handy tool,以帮助您使用语法。

接下来,让我们安排从jobs.json和显示通知
由于它应该在bot.js脚本之后运行,因此我们将其安排在bot.js脚本后3分钟运行。因此,我们将在crontab文件中添加以下行:

*/10 * * * * /path/to/notifyjobs.sh

这样,我们就完成了!我们已经成功创建了一个机器人来从UPWORK中提取作业列表,并将其显示为MACOS通知。现在,我们可以从我们自己的桌面屏幕的舒适度中获得最新的工作更新。不再有无休止的滚动工作清单 - 机器人为我们带来了所有艰苦的工作!因此,继续前进,放松,让机器人为您求职。狩猎快乐!