[自動登入避開 recaptcha] 利用selenium-webdriver 製作MeWe Scraper

Clipversity
13 min readDec 28, 2020
我寫了一個mewe scraper

原文: https://clipversity.com/article/-by-pass-recaptcha--Use-selenium-webdriver-to-make-MeWe-Scraper-MOL6Ledf35RHzZ6Pxqw

隨着MeWe 越來越多人使用,有不少開發者都在思考到底MeWe能否成為下一個Facebook,然而這不是本文章要討論的內容,而今次想與大家分享一下Web Scraper。

如果大家有用過MeWe都知道它需要登入才可以看到專頁或群組的貼文,加上MeWe主張資料安全及沒有廣告,因此它們並沒有提供API讓我們使用。換言之,自行寫一個Web Scraper是唯一一個方法。

最後的目標

如果需要製作MeWe Scraper,我們的Scraper需要做到以下兩個條件:

第一步: Scraper需要自動登入 (需要處理recaptcha問題)

第二步: Scraper可以自動填入文字及向下滾動

現時的Web Scraper

今次我們會使用selenium web node作為Web Scraper,我會解釋為甚麼要用它及我試用過其他Web Scraper的情況。

Chrome的網頁Scraper — webscraper.io

一開始的時候,我打算使用webscraper.io作為MeWe 的Scraper,然而我其後發現MeWe必須要登入及不能夠自動在Text Field上輸入文字。雖然有些網頁的Text Field可以透過Javascript的方式填入文字然後觸發搜尋功能,但由於MeWe的搜尋欄位是利用Javascript 控制,因此必須模擬鍵盤的輸入。

Node 程式庫 — puppeteer 及 chrome-aws-lambda

puppeteer是Google開發的headless Chrome,Headless Chrome 即是在伺服器執行Chrome,我們可以通過命令行界面控制Chrome作不同自動化工作。

而 chrome-aws-lambda與puppeteer 十分相似,是簡化版的puppeteer,多用於在後台模擬瀏覽器。

它們的功能也十分豐富,例如在Nodejs中可透過

let page = await browser.newPage();
await page.goto('https://example.com');

開啟一個分頁並前往 https://example.com。

你也可以選擇等待的時間才到下一個步驟。

有興趣了解更多可參考這裏的github連結

puppeteer/puppeteer

alixaxel/chrome-aws-lambda

puppeteer 及 chrome-aws-lambda最後都沒有使用的原因是因為它們在執行Scraper的工作沒selenium方便,而且selenium的速度都比兩者快。

selenium-webdriver

這一次我將會使用nodejs版的selenium-webdriver,它也擁有python版,語法是差不多,大家可以放心。

第一步,建立新的Node專案

npm init

完成一些名稱的設定後,你可以安裝MeWe Scraper需要用到的package。

npm i -g chromedrive
npm i selenium-webdriver

第二步,建立start.js

我們會將所有程式都寫在這裏,我們需要作三個動作:

  1. 開啟MeWe.com 並自動填入帳號及密碼
  2. 使用「人手」的方法避開 recaptcha,完成登入
  3. 在MeWe的搜尋欄填入搜尋字串,然後取得搜尋結果。

start.js

const {Builder, By, Key, until} = require('selenium-webdriver');
const fs = require('fs');
const C = {
username: "YOUR_MEWE_ACCOUNT",
password: "YOUR_MEWE_PASSWORD"
};
const start = async (searchStr) => {
let driver = await new Builder().forBrowser('chrome').build();

/*
三個動作
*/
}
start("香港人")

第一個動作 — 開啟MeWe.com 並自動填入帳號及密碼

await driver.get('http://www.mewe.com/');   //開啟MeWe.com 
await driver.wait(until.elementLocated(By.xpath('//*[@id="login-fake-btn"]')), 10000);
await driver.findElement(By.xpath('//*[@id="login-fake-btn"]')).click();
await driver.sleep(100);
await driver.findElement(By.xpath('//*[@id="email"]')).sendKeys(C.username); //自動填入帳號
await driver.sleep(100);
await driver.findElement(By.xpath('//*[@id="password"]')).sendKeys(C.password); //自動填入密碼
await driver.findElement(By.xpath('//*[@id="login-overlay"]/div/form/button')).click(); //自動登入
await driver.wait(until.elementLocated(By.xpath('//*[@id="ember24"]')), 20000); //等待直至完成登入

第二個動作 — 使用「人手」的方法避開 recaptcha,完成登入

由於MeWe在登入界面中有recaptcha,我們需要「手動」完成recaptcha後才會執行下最後一句的程式。

外國有些網頁提供「自動別人手」或其他程式庫完成recaptcha,這裏不作太多介紹。

第三個動作 -在MeWe的搜尋欄填入搜尋字串,然後取得搜尋結果。

start.js

...
await new Promise(r => setTimeout(r, 1000));
await driver.findElement(By.xpath('//*[@id="ember24"]')).sendKeys(searchStr, Key.RETURN);
await driver.sleep(500);
await driver.findElement(By.xpath('//div[text() =\'Groups\']')).click();
await driver.sleep(1000);
let scrollHeight = 2407
let numberOfLoop = 5
for (let i = 0; i < numberOfLoop; i++) {
await driver.executeScript("document.getElementsByClassName(\"smart-search_result smart-search_result--groups win-scrollbar\")[0].scrollBy(0, " + scrollHeight + ")")
await driver.sleep(1500);
}
let scrollResult = await driver.findElement(By.className('smart-search_result smart-search_result--groups win-scrollbar'))
let children = await scrollResult.findElements(By.className('smart-search_group c-mw-smart-search-group ember-view'))

在上述的程式碼中,我們會自動選擇群組的分類(專頁的數據之後在github更新)。由於MeWe每一次只列出30個結果,因此我們需要利用selenium-webdriver 自己在指定的時間向下滾動以取得更多結果。

我們在搜尋中最少找到每一個群組的五個數據:

  • 群組圖片
  • 群組名稱
  • 群組連結
  • 加入群組人數
  • 加入類型(公開/非公開)

因此我們可以利用Forloop的方式取得這些數據

let jsonArr = []
let allPromise = []
if (Array.isArray(children)) {
children.map(async webEle => {
allPromise.push(new Promise(async (resolve, reject) => {
let imgDom = await webEle.findElement(By.className("profile_img usr-avatar-small"))
let aDom = await webEle.findElement(By.className("smart-search-group_img ember-view"))
let titleDom = await webEle.findElement(By.className("h-trim ember-view"))
let numberOfMemberDom = await webEle.findElement(By.className("smart-search-group_members"))
let groupType = await webEle.findElement(By.className("h-flex_center_x_y"))
jsonArr.push({
url: await driver.executeScript("return arguments[0].attributes['href'].value", aDom), //return join link
imageSrc: await driver.executeScript("return arguments[0].attributes['src'].value", imgDom), //return image src from img dom,
title: await driver.executeScript("return arguments[0].innerText", titleDom), //return group title,
numberOfMember: parseInt(await driver.executeScript("return arguments[0].innerText.split(\"Members (\")[1].split(\")\")[0]", numberOfMemberDom)), //return numberOf member,
public: await driver.executeScript("return arguments[0].innerText", groupType) === "Join Group", //return is a public group,
description: "",
country: "",
category: "",
subCategory: "",
})
resolve()
}))
})
}
Promise.all(allPromise)
.then(res => {
fs.writeFile('example.json', JSON.stringify(jsonArr), 'utf8', function(err) {
if (err) return console.log(err);
});
})
.catch(err => console.log(err))

當取得數據後,打包為json格式然後輸出到我們的專案中。

示範結果:

[
{
"url": "/group/5fc36bfa7f1d500f69a484be",
"imageSrc": "https://img.mewe.com/api/v2/group/5fc36bfa7f1d500f69a484be/public-image/5fcc51ebda6a0364ec119b82/400x400/img",
"title": "香港人里數/獎賞/旅遊分享區",
"numberOfMember": 816,
"public": false,
"description": "",
"country": "",
"category": "",
"subCategory": ""
},
{
"url": "/group/5fbe3d3ec057695a0a69610a",
"imageSrc": "https://img.mewe.com/api/v2/group/5fbe3d3ec057695a0a69610a/public-image/5fbe3d3e67b8dd74597c2ace/400x400/img",
"title": "香港人@英國💛互助圈",
"numberOfMember": 263,
"public": false,
"description": "",
"country": "",
"category": "",
"subCategory": ""
}
]

Github連結

如果你希望貢獻,你可以前往我的github專案分享你的想法喔😜。你也可在下方拍一拍手支持我們。

--

--