Simply Sneaker Bot with Playwright - มาลองเขียนบอทง่ายๆตามสไตล์คนขี้เกียจกันเถอะ
Tags: webscraping, playwright, puppeteer, node
Published: 2020-07-01 13:23:38

> **เพราะฉัน "ขี้เกียจ" ฉันเลย "เขียนโค้ด"**

##Introduction 😇
หากเล่าถึงความขี้เกียจนั้น หลายๆคนคงเข้าใจความรู้สึกนี้ดีโดยไม่ต้องอธิบายอะไรเลยแม้แต่น้อย เราเกิดมาในยุคที่บางครั้งผู้ใหญ่จะสอนเราว่า "อย่าขี้เกียจนะ มันไม่ดี" ทั้งๆที่บางครั้งก็ควรจะปล่อยให้เป็นตามธรรมชาติบ้าง คือบางทีเราเหนื่อยมาทั้งวันละ พรุ่งนี้ขอขี้เกียจหน่อย ตื่นสายๆ นอนกลิ้งไปมาเพลินๆ ไม่มีอะไรเสียหายหนิ ถ้าทุกอย่างจบที่คำว่า "ขี้เกียจ...แต่พอดี" ซึ่งบทความนี้ มันก็เกิดจากความขี้เกียจนี่แหละ ที่ทำให้เราต้องสร้างเครื่องทุ่นแรง เราจะได้มีเวลาไปขี้เกียจ.

##Idea 👟
ในโลกของ Sneaker นั้น รองเท้าสวยๆถือเป็นไอเท็มที่เหล่านักสะสมต้องแย่งกันมีไว้ครอบครอง รุ่นนี้จะออกวันไหน รุ่นนั้น collab กับใคร ตัวนี้อย่างเด็ด ซึ่งความต้องการเหล่านี้ทำให้ราคารีเซลรองเท้าบางรุ่นนั้น วิ่งพรวดขึ้นไปถึง 10 - 20 เท่า!ของราคาเดิมในวันเปิดตัว เลยเป็นที่มาของโจทย์ที่ว่า จะทำยังไงดีน้า ขี้เกียจลุกขึ้นมากดแข่งกับชาวบ้านตอน 9 โมงเช้าจริงๆ กดก็ได้บ้างไม่ได้บ้าง นก(วืด)นี่คือประจำ กว่าจะเลื่อนเม้าส์ไปกด, กรอกนู่นนี่, ใส่รหัสบัตรเครดิต, กดยืนยัน หมดพลังงานไปไม่น้อยเลยทีเดียว.

##Finding solution 🤔
มีอะไรบ้างที่ จะมากดซื้อรองเท้าให้เราได้ในตอนเช้าๆ
1. จ้างคนอื่นกด
2. ให้คนที่บ้านช่วยกด
3. เขียนบอทมากด
4. ไม่กด รวยแล้วรอซื้อราคารีเซลแม่มม 

แน่นอน เรายังไม่รวยพอ เราเป็นโปรแกรมเมอร์เราก็ต้องเขียนบอทมาช่วยกดสิ เราก็หาๆ Solution ซึ่งมันก็มีเยอะมากเลย แต่ในบทความนี้ผมขอเลือกเล่าวิธีน่ารักๆ ง่ายๆ โดยเราจะใช้พวก Browser Automation Framework มาช่วยเราสร้าง Bot ในการช๊อปปิ้งครั้งนี้
> _**Note**: บทความนี้ขอไม่เล่าถึงการเอา Proxy มาช่วย, การเจาะ iframe หน้าจ่ายเงิน, การหาที่ตั้ง server รองเท้า, การ Gen Cookies, การสู้กับ Captcha, Bot Protection และอื่นๆ บลาๆ นะครับ_ 

และแล้วก็เจอ **Playwright**
**Playwright** คือ Browser Automation Framework ตัวนึง มีความสามารถทำ Automate testing, Web Scraping, Automating tasks, Take Screenshot, and more. ซึ่ง Playwright เนี่ย จริงๆมันก็ถูกสร้างขึ้นด้วยทีมลีดเดียวกับที่สร้าง Puppeteer นะ เพียงแต่ย้ายมาอยู่ฝั่ง Microsoft จากเดิมที่อยู่กับ Puppeteer ที่เป็นของ Google ตัว Playwright เองมันมีความได้เปรียบกว่าตรงที่มันมี cross-browser support คือมันรองรับทั้ง WebKit, Firefox และ Chromium 

Playwright เองมันก็เหมาะกับการเอามาทำ automate กับพวก Single Page Apps, Progressive Web Apps เพราะความสามารถจำพวก Auto-wait for element, Intercept network activity, Native input events แบบเม้าคลิก, พิมพ์นู่นนี่, หรือกดปุ่มต่างๆบนคีย์บอร์ดได้รวมทั้งอีกหลายอย่างครับ แต่จากพลังที่กล่าวมาน่าจะเพียงพอสำหรับการสร้าง Bot มากด & กรอก แทนเราในตอนเช้าตรู่ได้แล้ว

##Prerequisites 🧰
* Knowledge of Javascript(Node.js)
* Web Scraping Skill
* Web Elements Digging Skill (Can I call "Debugging?")
* Lazy Skill

##Automation steps 🤖
1. Login
2. Go to item page
3. Wait for shoe size displayed
4. Select shoe size (by Click)
5. Click Buy item
---- _Example End Here_ ----
6. Wait for the Payment page (Checkout page)
7. Input CVV & Credit card detail
8. Click Continue button
9. Click Checkout button
10. Wish me luck!

##Coding 💻
มาลุยยยยยกันเลยยย!
1.มาสร้าง folder ไว้เก็บโค้ดกันก่อน เปิด Terminal เอาตั้งแต่ต้นเลยละกัน
```javascript
mkdir lazy_shopping_bot
cd lazy_shopping_bot
```
2.สร้าง package.json file ต่อเลย ตรงนี้ก็ใส่รายละเอียดของโปรเจคตามใจเราได้เลยนะ
```javascript
npm init
```
3.ทำการ install Playwright ลงในโปรเจคของเรา ตรงนี้รอนิดนึง เพราะมันจะโหลดพวก Chromium, Firefox, WebKit engine รอแป๊บบ แต่ไม่นาน
```javascript
npm install playwright
```
4.เสร็จจากข้างบนก็มาสร้างไฟล์สำหรับเขียน Bot กัน, ได้ไฟล์แล้วก็เปิดด้วย IDE ที่ตัวเองถนัดได้เลยครับ
```javascript
touch bot.js
```
5.ต่อด้วย Import Playwright แล้วก็สร้าง async ฟังชั่นก์เปล่าๆ ไว้รอลงรายละเอียด ตรงนี้ตัว Browser ผมจะใช้เป็นตัว Chromium นะครับ ถ้าอยากใช้อย่างอื่นก็ได้เหมือนกัน
```javascript
const { chromium } = require('playwright');

(async () => {
// Coding here
})();
```
6.รัน Browser, สร้าง Context แล้วก็ Page
```javascript
const browser = await chromium.launch({ headless: false });
const context = await browser.newContext({
    viewport: { 
        width: 1280, 
        height: 1140 
    }
});
const page = await context.newPage();
```
ตอนที่เรารัน Browser เราสามารถ run แบบ headless ได้ คือแบบไม่ได้แสดง UI ให้เราเห็น แต่กรณีนี้เผื่อเราอยากลุ้น Bot เราไปด้วย เราสามารถ set headless mode = false ได้ พอเราสร้าง browser เสร็จเราต่อด้วยการสร้าง context เอาไว้เก็บ page ของเราต่อ ซึ่ง 1 context ก็สามารถสร้างได้หลาย page เลย แต่ในตัวอย่างนี้เราสร้างหน้าเดียวพอครับ

7.มาแกะ Elements กัน 
ตรงนี้จะสำคัญหน่อย เพราะจะมีเทคนิคของการทำ Web Scraping เข้ามาเสริม คือเราต้องหา "ที่อยู่" ของปุ่มหรือกล่องข้อความที่จะให้ Bot ทำในสิ่งที่เราต้องทำแทนเรา เช่นการ Click หรือ Typing อะไรลงไป 

Playwright จะสามารถหา element หรือ node พวกนั้นได้ผ่านทาง Selector engine เช่น CSS selectors, XPath, Full XPath selectors, HTML Attributes ต่างๆ เช่นพวก id, class, text, placeholder and etc. พร้อมทั้งยังมีความสามารถ Auto-wait ที่ช่วยให้รอ element นั้นปรากฎขึ้นมาก่อน ถึงจะเรียกคำสั่งที่กระทำกับ element นั้นให้.
เราสามารถหา element ที่เราต้องการผ่านทาง Developer tool -> Inspect Element ของ Browser ที่เราใช้ โดยการเปิดจาก
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/tze1tv5dw0oa9norguo0.png)
จากรูปด้านล่าง สมมติว่าเราหา Element ที่เราต้องการจนเจอแล้ว เราสามารถใช้ตัวช่วย ที่จะหา path ของ element หรือ node ที่เราต้องการ 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/4q9hu8d6v2tx3jliqor0.png)
พอเราเจอปุ่มหรือกล่องข้อความที่ต้องการ เราก็ทำการ Copy path ที่เราได้จากข้างบน มาเก็บไว้ในโค้ดเราได้เลยครับ (ตัวอย่างจะเก็บในแบบของ Full XPath)
```javascript
// Login elements full XPath
const loginToSystemButtonPath = '//html/body/div[2]/div/div/div[1]/div/header/div[1]/section/ul/li[1]/button';
const loginEmailBoxPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[2]/input';
const loginPasswordBoxPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[3]/input';
const loginSubmitButtonPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[6]/input';

// Submit to buy element
const submitToBuyButtonPath = '//html/body/div[2]/div/div/div[1]/div/div[3]/div[2]/div/section[1]/div[2]/aside/div/div[2]/div/button';
```

หรือเราจะทำท่านี้ก็ได้ เช่น อยากให้ Click element ที่มี Text ที่เราต้องการ
```javascript
const shoeSize = '44';
await page.click('text=EU '+shoeSize);
```
ตัวอย่างข้างบนก็จะเป็น "รอ" แล้ว Click ที่ element นั้นทันที ถ้าเจอ Text ที่มีคำว่า EU 44

8.ประกอบร่าง
เมื่อเราได้เหล่า elements ที่เราต้องการครบเรียบร้อยแล้ว เราก็มาเรียง action ตามแพลนที่เราวางไว้ จะเห็นว่าฟังก์ชั่นส่วนใหญ่ ก็จะใช้อย่างตรงไปตรงมา ส่วนตัวผมคิดว่าอ่านง่ายพอสมควรครับ
```javascript
const { selectors, chromium } = require('playwright');

(async () => {
    const browser = await chromium.launch({ headless: false });
    const context = await browser.newContext({
        viewport: { 
            width: 1280, 
            height: 1140 
        }
    });
    const page = await context.newPage();

    const itemLink = 'Let you guess :P';
    const user = {
        email: '',
        password: ''
    };

    // Go to an item and login
    await page.goto(itemLink);

    // Login elements
    const loginToSystemButtonPath = '//html/body/div[2]/div/div/div[1]/div/header/div[1]/section/ul/li[1]/button';
    const loginEmailBoxPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[2]/input';
    const loginPasswordBoxPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[3]/input';
    const loginSubmitButtonPath = '//html/body/div[2]/div/div/div[2]/div/div/div/div/div/div/div/div/div/div/div/div/div/div/div[2]/form/div[6]/input';

    // Login into the system action
    await page.click(loginToSystemButtonPath);
    await page.type(loginEmailBoxPath, user.email);
    await page.type(loginPasswordBoxPath, user.password);
    await page.click(loginSubmitButtonPath);

    // Wait and select shoe size
    const shoeSize = '44';
    await page.click('text=EU '+shoeSize);

    // Submit to buy element
    const submitToBuyButtonPath = '//html/body/div[2]/div/div/div[1]/div/div[3]/div[2]/div/section[1]/div[2]/aside/div/div[2]/div/button';
    await page.click(submitToBuyButtonPath);

    // Do the next steps
    /*
    6. Wait for the Payment page (Checkout page)
    7. Input CVV & Credit card detail
    8. Click Continue button
    9. Click Checkout button
    10. Which me luck
    */
})();
```

##TESTING 💊
จาก Code ด้านบนเราก็มาลองรันดูได้เลยครับ ด้วยคำสั่ง
```javascript
node bot.js
```
พอรันแล้วก็จะพบกับหน้าที่ว่างเปล่า พร้อม error log ผ่ามๆๆ
เพราะเราไม่มี itemLink ของสินค้าจริงๆมาแปะให้ลองครับ

##SUMMARY 🔮
สุดท้ายแล้ว มันเป็นแค่การทำอะไรสนุกๆ เพราะความขี้เกียจ เลยแค่อยากลองทำดู พร้อมทั้งเป็นไกด์ให้กับคนที่สนใจ อยากลองทำ Automation หรือ Testing อะไรสักอย่างกับเว็บของตัวเองดู เพราะการทำพวก Bot หรือ Web Scraping แนวนี้ ผมรู้สึกว่ามันไม่ค่อยขาวสะอาดมากเท่าไหร่ครับ มันเลยมีทั้งเทคนิค, ความยากและซับซ้อนกว่าที่ผมเล่าไว้ข้างบนเยอะเลย

ส่วนสำหรับคนที่สนใจการทำ Web Automation กับ Playwright หรือ Web Scraping ก็ลองเข้าไปอ่านได้ที่ลิ้งที่ผมแปะไว้ให้ด้านล่างนี้ได้เลยครับ
* [Playwright](https://github.com/microsoft/playwright)
* [Web Scraping](https://en.wikipedia.org/wiki/Web_scraping)
* [Xpath](https://devhints.io/xpath)

ก่อนจากกัน...ฝาก page สนุกๆไว้ให้ติดตามผ่านทาง Facebook กันอีกทางครับ
[https://www.facebook.com/igroomgrimonthefloor/](https://www.facebook.com/igroomgrimonthefloor/)

อยากให้ทุกคนสนุกกับการเขียนโค้ดครับ
Be lazy & Happy Coding 🤤🖥🤗