Mình là lập trình viên java. Vừa rồi mình có đọc được một yêu cầu tuyển việc, có cái title là "Cần người code tool đổi mật khẩu gmail tự động".
Nội dung cụ thể là "Tôi có khoảng 5000 tài khoản gmail, bao gồm username và password, tôi cần đổi tất cả mật khẩu cũ sang mật khẩu mới, ai code được tool, pm thương lượng..."
Note: đọc tới đây, nếu có ai thắc mắc kiểu sao lại có nhiều gmail thế, đổi pass với mục đích tốt hay xấu ?... thì vấn đề này xin phép được bàn sau ạ = > cứ đơn giản coi như đây là một bài toán cần lời giải.
Đứng trước vấn đề này, mình có nghĩ tới vài giải pháp trong đầu, nhưng cái nào cũng không ổn, cuối cùng mình làm theo cách code java dùng thư viện SELENIUM (nghe bảo dân QA, Tester dùng nhiều lắm, không biết phải không). Và khi code xong, mọi thứ chạy "âu tu mây tịc" đúng như mình mong chờ, cảm giác sung sướng cứ như vừa tìm ra Châu Mỹ :)) (đâu biết rằng Châu Mỹ đã có người tìm ra lâu lắc rồi TT ).
Thực ra tự cá nhân mình thấy cách này, tuy giải quyết được bài toán, nhưng nó hơi chậm => cảm giác chưa "ngon" lắm => hi vọng pro nào vãng lai qua, thả em ít keyword tốt hơn. :3

Các giải pháp

Trước khi show code mình xin điểm lại các giải pháp mà mình đã nghĩ tới:
1. Phần mềm lặp lại thao tác chuột và bàn phím.
Nghĩ tới đầu tiên, mình định dùng phần mềm KeyBoard And Mouse Record - nôm na là cái phần mềm này nó ghi nhớ lại thao tác với chuột và bàn phím, mình chỉ cần làm mẫu một lần, sau đó nó sẽ thực hiện lặp lại các thao tác đó, hoàn toàn tự động. Phần mềm này trước mình dùng để treo game nên nhớ cái liền. Cơ mà có vài cái không ổn. Thứ nhất thằng google này cùng một url, bình thường truy cập ra một giao diện, nhưng có lúc truy cập lại ra một giao diện khác. Ví dụ như thẻ "Change Password", bình thường nó nằm ở góc trái, nhưng có lúc nó lại nằm ở góc ... trái và bị lệch lên trên vài inch/ pixcel. => Nếu dùng tool này sẽ có case bị hụt vì click không đúng chỗ. Chưa kể một đống các vấn đề phát sinh như căn chỉnh thời gian load của đường truyền mạng khớp với thao tác chuột/ bàn phím, hay các vấn đề khi thay đổi kích thước màn hình, device...

2. Code với thư viện JSOUP.
Mình là lập trình viên, mình phải tận dụng skill này => quyết định code tool hẳn hoi. Chợt nhớ tới ngày xưa có làm một cái project thu thập tin tức từ các website bằng thư viện JSOUP của java. Cơ mà làm cách này cảm thấy rất tù tội, nhất là cái khoản quản lý cookie, session gửi lên rồi nhận về. POST/GET...vv.
Bản chất thằng Jsoup nó chỉ giỏi bóc tách source thôi. Làm cách này không khéo, chắc chắn gmail sẽ nhận được một đống thông báo kiểu phát hiện truy cập bất thường, rồi bắt nhập câu hỏi bảo mật...bla bla.

3. Dùng tiện ích / Add On/ Extension của Web Browser
Hay là dùng IMACROS nhỉ? Một add on của các web browser, Add on này vừa có thể tự động tạo script nhờ nhìn thao tác chuột của người dùng, và dev cũng có thể tự viết script, có hỗ trợ nhúng cả JS nữa...
Đây cũng là một giải pháp. Cơ mà mình ngồi chiêm nghiệm, giờ code Java còn chưa rành, lại đi học code đặc thù cho IMACROS để giải quyết mỗi bài toán này, thấy nó không phấn khởi cho lắm.

4. Dùng API, thư viện Development Kit của Google
Mấy thằng lớn như google, chắc thể nào cũng cung cấp thư viện, các KIT để developer tương tác qua API.
=> 1 suy nghĩ màu hồng xuất hiện => mình sẽ dùng cái thư viện của Google, chắc sẽ là google.jar, và trong đó sẽ có 1 hàm là .changePassword("newpassword").
=> Vậy là xong, quá easy.
Cơ mà sau khi search google đủ các thư viện, kit của google, thì chả thấy có. Có chăng thì cũng chỉ có 1 cách để developer tương tác với các service của Google, là dùng token, secret_key thay cho username + pass, mà mấy cái giá trị này muốn có thì phải đăng nhập trên web browser để lấy. => Phũ.
Đang không muốn động tay động chân, muốn mọi thứ tự động hóa, giờ lại bảo đăng nhập vào web để lấy key thì quá tội.

5. => Và rồi...
Tèn ten ta ten, tèn tén tèn ten...

_____________________SELENIUM__________________

Vô tình bắt gặp thằng này, vậy Selenium là gì?...
Google ra nhiều lắm, nôm na thì nó là thư viện, hỗ trợ rất nhiều ngôn ngữ, trong đó có java, dùng để code tự động thao tác mọi thứ, như 1 user + web browser thông thường.

Giải bài toán với Selenium

Sau đây mình xin phép trình bày lại cách mình vừa học vừa làm với thằng này. Mình trình bày khá tỉ mỉ, và dạt dào cảm xúc, nên ae nào rảnh, làm theo luôn cho vui =))
1. Thư viện.
Có thể import file lib với việc download trên mạng bằng từ khóa "selenium-java" , hoặc dùng maven sau:

 <!-- https://mvnrepository.com/artifact/org.seleniumhq.selenium/selenium-java -->
<dependency>
    <groupId>org.seleniumhq.selenium</groupId>
    <artifactId>selenium-java</artifactId>
    <version>3.4.0</version>
</dependency>

2. Pre Code
Set môi trường

import org.openqa.selenium.*
.....
System.setProperty("webdriver.chrome.driver", "/home/tungtv/chromedriver");  
WebDriver driver = new ChromeDriver();

Lúc đầu mình định sử dụng phantomjs (1 thư viện vi diệu), chứ không định dùng ChromeDriver hay FirefoxDriver.
Vì phamtomjs có lợi thế là tất cả mọi thứ chạy ngầm background, không hiển thị bất cứ UI nào ra màn hình. Nhưng trong quá trình code, mình gặp một khó khăn là có một số source code của google, mà thằng phantomjs không thể nào xử lý được, đã search bug này khắp nơi, thấy khá nhiều người gặp phải, mà chưa thấy ai trả lời được => Thôi cái gì khó quá làm sau. :))
Note: Có lẽ phantomjs sẽ là sự lựa chọn tuyệt vời với các website thông thường, nhưng lại là không đối với anh google?
Quay lại vấn đề: với việc new ChromeDriver(); thì khi run code, trình duyệt Chrome cài đặt sẵn trên máy tính sẽ chạy lên auto.

3. Start code
Mở chrome lên rồi, thì truy cập vào URL đang nhập Google nào:

driver.get("https://accounts.google.com");

(chửi thầm), mịa hiện 2 ô username và password để nhập 1 lần luôn đi, lại còn bày vẽ bắt valid account trước, thành công thì mới tới bước nhập password. Thật là lắm sẹo, mấy web site thông thường, có thấy thằng nào chơi kiểu vầy đâu.
Chuột phải, rồi inspect lên, xem các thẻ code nào:
Cái thẻ input username có id là "identifierId".

Khai báo element luôn.

WebElement userNameElm = driver.findElement(By.id("identifierId"));

Ngoài By.id(), có thể dùng By.name(), By.tagName(). By.xPath()...vv..
Tùy vào source code mà lựa chọn By nào cho phù hợp.
Điền tài khoản gmail vào cái element này:

userNameElm.sendKeys("tungtv202@gmail.com");
  • Xem thẻ id của button "TIẾP THEO" nào

    So easy
driver.findElement(By.id("identifierNext")).click();

Cho nó nghỉ ngơi 1 tý, mạng cùi load lâu mà.

Thread.sleep(2000); 
  • Đây rồi, sang ô nhập mật khẩu.

    Quái nhỉ, sao tag mật khẩu, lại không có ID như cái tag account_name vừa nãy nhỉ. :(
    Thử với By.name xem, có ra cái trò trống gì ko.
driver.findElement(By.name("password")).sendKeys("mat_khau");

May quá, pass rồi. =))
Tiếp tục thôi

driver.findElement(By.id("passwordNext")).click();
  • Check valid account
    Bây giờ confirm lại lần nữa là account_name với password mình vừa input trong code là hàng chuẩn. (Phòng khi file 5000 gmail kia, có cái bị sai ngay từ đầu)
    Suy nghĩ một hồi, nếu mà đăng nhập thành công, thì thằng google sẽ direct tới 1 trang mới, mà tại trang mới này có hiện dòng chữ là "My Account gives you" hoặc "Tài khoản của tôi cho phép".
    Ngược lại, nếu không hiện 2 chuỗi string trên, thì chứng tỏ là sai pass.
String key = "My Account gives you";
String key2 = "Tài khoản của tôi cho phép";
Thread.sleep(2000);
String pageSource = driver.getPageSource();
Boolean isHave = pageSource.contains(key) || pageSource.contains(key2);
System.out.println(userName + " : " + isHave);
 driver.navigate.to("https://myaccount.google.com/signinoptions/password");

Mình chả hiểu cái driver.get(url) với cái (url) khác gì nhau, thấy cả 2 cái đều là truy cập tới 1 URL

  • Lại bắt nhập mật khẩu 1 lần nữa này.
driver.findElement(By.name("password")).sendKeys("mat_khau");
driver.findElement(By.id("passwordNext")).click();
Thread.sleep(1000);
  • Bắt nhập mật khẩu mới...những 2 lần.

    Quái lạ, sao lại không có cả id, cũng chả có name, sao không có cái gì để định danh thẻ vậy trời. Nhìn tên mấy cái class với name, mà có cảm giác như mấy cái này là sinh ngẫu nhiên, tự động random vậy.
    Thấy nghi nghi => tốt nhất là không đụng tới.
    Thử với tag input xem sao
driver.findElement(By.tagName("input")).sendKeys("new_password")


Quái, sao nó lại điền mỗi form trên, còn cái form dưới của tui đâu rồi TT.
À à, có 2 thẻ input luôn mà. Phải dùng List

 List<WebElement> elements = driver.findElements(By.tagName("input"));
         for (WebElement e: elements){
             e.sendKeys("new_password");
         }

Ngon lành cành đào, bây giờ lại chỉ cần .click cái button là xong. => So easy 3

Quái, sao cái thẻ button "ĐỔI MẬT KHẨU" này, lại chả có name, chả có id, gì vậy trời, chả nhẽ lại dùng By.tagName("span"), cái tag span này thì vô vàn. Biết index cái nào trời.
Thiệt tình thằng google này...
À, mình thử gửi event bấm phín Enter xem sao. Bình thường mình cũng toàn nhập mật khẩu xong ENTER, chứ có mấy khi di chuột Click đâu.

elements.get(1).sendKeys(Keys.ENTER);

Perfect! >>> ALL DONE.

Kết bài

Vậy là xong, giờ chỉ cần viết cái vòng lặp, đọc file text chứa username, pass theo line để xử lý, hoặc cao siêu hơn, đọc từ file Excel theo row.
So Easy.
1 phút ngoài lề...để...Chém gió tầm vĩ mô
Bài toán crawler nói chung, hay bài toán gmail này nói riêng, việc code này thực sự rất bị động, vì nếu một mai google đổi source code, thì gần như đoạn code ngày hôm nay mình viết là vô dụng. Liệu có một giải pháp nào để mọi thứ có thể tự học hỏi được không?
Liệu có thể code được 1 tool, tự động nhận diện được tất cả các website, không chỉ mỗi google?, chỉ cần nhả URL bất kỳ vào, tự động phân tích và xử lý tất cả?
Tại sao cùng chỉ là kiểu nhập text mà thằng Google lúc thì kiểu Id, lúc kiểu name, chả thấy chung format gì cả? Liệu sau này code web, mình có nên bắt chiếc cách code này?
Mà cũng có khi mình đa nghi quá, có khi chỉ đơn giản là 2 module này ở Google là 2 team khác nhau? mỗi team thích 1 kiểu =))