Python for crawler - Part 2 - Tự động lấy dữ liệu từ tiki.vn sử dụng Selenium

Crawl Tiki.vn

Why?


Ở bài trước chúng ta đã thực hành crawl từ dantri.vn, Sử dụng thư viện beautifullSoup, chỉ cần vài dòng code đơn giản là ta đã lấy được danh sách các bài post từ dantri.vn.
Tuy nhiên, nếu bạn hứng lên và đòi lấy các sản phẩm để post lên 1 trang như https://hagia.com.vn thì sao? Nếu bạn áp dụng các kiến thức từ bài hôm trước thì sẽ có vấn đề như sau:
Đại khái là sẽ không lấy được sản phầm nào cả!!!
Tại sao vậy, Tại vì tiki và lazada là hai trang bán hàng trực tuyến, học có nhiều kỹ thuật để làm website chạy nhẹ hơn, nhanh hơn, tối ưu hơn ( ví dụ dùng js để load động các sản phẩm).
Khi đó nếu mình chỉ dùng thư viện requests để lấy source vê thì sẽ chẳng được gì cả (hình ở trên là đã có cả css rồi đó, nếu chỉ hiển thị source lấy về thì còn thảm hại hơn nữa)
Vấn đề trên không chỉ xảy ra với tiki, lazada mà còn rất rất nhiêu website khác như vậy, và có cả nhưng trường hợp phức tạp hơn khi làm crawler, ví dụ lấy bài viết trong voz thì sao. Đôi khi cần phải đăng nhập, chọn category... đủ thứ mới lấy được dữ liệu mình cần.
Nếu dùng cách đơn giản như bài trước thì sẽ không xử lý được.

How?

Với những trường hợp phức tạp như thế này hoặc hơn thế nữa. Ta phải dùng selenium để chạy tự động giống như một người dùng bình thường, mở chrome hoặc firefox, gõ link duyệt web...Nói chung như một người dùng thực thụ, chỉ khác là mọi thứ đều được tự động.
        Điều đó có thể làm được nếu dùng selenium, selenium sẽ sử dụng chính trình duyệt chrome để duyệt web, và cho phép mình thao tác với chrome bằng code python.

Cài đặt Selenium

Việc cài đặt selenium tôi sẽ không đi sâu, vì nó đã có hướng dẫn rất kỹ tại https://www.seleniumhq.org/
Đại khái bạn phải down một file gọi là driver, (muốn sử dụng chrome thì down chrome driver, tương tự cho firefox driver)
Sẽ có câu hỏi là tại lại cần có chrome driver đứng giữa làm gì, sao python app ko connect thẳng đến chrome luôn mà điểu khiển cho nhanh, qua thằng trung gian thì có lợi ích gì?
        Tất nhiên cái gì cũng có lý do của nó cả, chrome driver sẽ giúp đồng nhất api để code của mình có thể chạy cả với chrome, firefox, phantomjs,....và nhiều lý do khác nữa!

Sử dụng trong python để lấy danh sách sản phẩm của tiki

Sử dụng request - sẽ không lấy được sản phẩm nào

import requests
from lxml import html
from selenium import webdriver
class HtmlGetter:
   def get_html(self, url):
       pass
class HtmlParseGetter(HtmlGetter):
   def __init__(self, subject):
       self.subject = subject
   def get_html(self, url):
       html_source = self.subject.get_html(url)
       html_element = html.fromstring(html_source)
       return html_element
class RequestHtmlGetter(HtmlGetter):
   def __init__(self):
       pass
   def get_html(self, url):
       headers = {'User-Agent''Chrome/39.0.2171.95 Safari/537.36'}
       r = requests.get(url, timeout=15headers=headers)
       return r.content.decode('utf-8'errors='ignore')
if __name__ == '__main__':
   url = 'https://tiki.vn/deal-hot?src=header_label&_lc=Vk4wMzQwMjAwMDM%253D&tab=now&page=1'
   html_getter = HtmlParseGetter(SeleniumHtmlGetter( ))
   html_tree = html_getter.get_html(url)
   for in html_tree.xpath("//a[@class='deal-item']"):
       print(v.xpath("./div[@class='title']/text()"))
       print(v.xpath(".//span[@class='price-regular']/text()"))
       print(v.xpath(".//div[@class='price-sale']/text()"))
       print(v.xpath("./div[@class='image']//img/@src"))
       print()
- Ở đây tôi có tạo 1 base class HtmlGetter để tạo 1 giao diện thống nhất cho việc get html từ một url.
- HtmlParseGetter: Lớp này sẽ parse html string thành DOM tree, để sử dụng xpath query dữ liệu
- RequestHtmlGetter: Lớp này là lấy html theo cách đơn giản, chính là lấy theo cách ở bài 1, sử dụng request để lấy. Tôi đưa lớp này vào chạy thử để so sánh thấy được kết quả khác nhau giữa việc sử dụng requests và selenium.
Nào ta cùng chạy đoạn code trên, kết quả như sau:
Không có gì được in ra, vậy là không lấy được sản phẩm nào cả.
=> do các sản phẩm được javascript lấy và render, nên dùng request thì javascript không được chạy nên chẳng có sản phẩm nào hiện ra cả.

Sử dụng selenium mô phỏng người dùng duyệt web


from lxml import html
from selenium import webdriver
class HtmlGetter:
   def get_html(self, url):
       pass
class HtmlParseGetter(HtmlGetter):
   def __init__(self, subject):
       self.subject = subject
   def get_html(self, url):
       html_source = self.subject.get_html(url)
       html_element = html.fromstring(html_source)
       return html_element
class SeleniumHtmlGetter(HtmlGetter):
   def __init__(self, scroll_to_bottom=False):
       self.scroll_to_bottom = scroll_to_bottom
   def get_html(self, url):
       browser = webdriver.Chrome()
       browser.maximize_window()
       browser.get(url)
       html_source = browser.page_source
       browser.quit()
       return html_source
if __name__ == '__main__':
   url = 'https://tiki.vn/deal-hot?src=header_label&_lc=Vk4wMzQwMjAwMDM%253D&tab=now&page=1'
   html_getter = HtmlParseGetter(SeleniumHtmlGetter( ))
   html_tree = html_getter.get_html(url)
   for in html_tree.xpath("//a[@class='deal-item']"):
       print(v.xpath("./div[@class='title']/text()"))
       print(v.xpath(".//span[@class='price-regular']/text()"))
       print(v.xpath(".//div[@class='price-sale']/text()"))
       print(v.xpath("./div[@class='image']//img/@src"))
       print()
- SeleniumHtmlGetter: Lớp này có chức năng lấy html bằng selenium, cũng khá đơn giản phải không.
+ Mở chrome, xong maximize window
+ Ra lệnh cho chrome đi đến url tiki, Điều kì diệu chính là ở đây, khi dùng selenium thì trang web được load như một cách thông thường, mọi javascript sẽ được chạy, css sẽ được render.
+ Lấy htm đang có sẵn ở trên trình duyệt.
Sau khi chạy file trên
Đã ra các sản phẩm, có vẻ ổn đó, giống hệt trên web.
Bình tĩnh, xem tiếp những gì được in ra trên console xem.
cái gì kia, không phải là link ảnh nữa mà một chuỗi gì đó liên quan đến base64. Đó là một vấn đề khác mà ta sẽ xử lý trong phần tiếp.

Sử dụng selenium mô phỏng người dùng duyệt web - Như thật luôn

Đoạn code trên không có vấn đề gì, mà vấn đề là ở site tiki.vn, họ dùng js để load ảnh, trong html trả về sẽ chỉ trả về 1 cái ảnh mặc định có kích thươc 1 pixel, chính là cái base64 bạn nhìn thấy ở trên. Sau đó khi người dùng scroll đến đâu thì javascript sẽ fill ảnh đến đó. Điều này là cho trang web nhẹ hơn. Nhưng lại làm khó cho mình.
Để lấy được hết các link ảnh, mình sẽ cho chrome scroll như người thật luôn. Sửa lớp SeleniumHtmlGetter như sau:
from lxml import html
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
class HtmlGetter:
   def get_html(self, url):
       pass
class HtmlParseGetter(HtmlGetter):
   def __init__(self, subject):
       self.subject = subject
   def get_html(self, url):
       html_source = self.subject.get_html(url)
       html_element = html.fromstring(html_source)
       return html_element
class SeleniumHtmlGetter(HtmlGetter):
   def __init__(self, scroll_to_bottom=True):
       self.scroll_to_bottom = scroll_to_bottom
   def get_html(self, url):
       browser = webdriver.Chrome()
       browser.maximize_window()
       browser.get(url)
       if self.scroll_to_bottom:
           last = None
           for in range(500):
               for in range(5):
                   browser.find_element_by_xpath('//html').send_keys(Keys.DOWN)
               if last is not None and last == browser.execute_script('return window.pageYOffset;'):
                   break
               last = browser.execute_script('return window.pageYOffset;')
       html_source = browser.page_source
       browser.quit()
       return html_source
if __name__ == '__main__':
   url = 'https://tiki.vn/deal-hot?src=header_label&_lc=Vk4wMzQwMjAwMDM%253D&tab=now&page=1'
   html_getter = HtmlParseGetter(SeleniumHtmlGetter( ))
   html_tree = html_getter.get_html(url)
   for in html_tree.xpath("//a[@class='deal-item']"):
       print(v.xpath("./div[@class='title']/text()"))
       print(v.xpath(".//span[@class='price-regular']/text()"))
       print(v.xpath(".//div[@class='price-sale']/text()"))
       print(v.xpath("./div[@class='image']//img/@src"))
       print()
Lưu ý chỗ tự động scroll_to_bottom nhé. Đoạn code này sẽ tự động scroll chrome đến khi không scroll được nữa. sau đó mới lấy html.
Sau khi chạy code trên kết quả có thể nói là mĩ mãn.

Problem

Hiện tại mọi thứ cũng khá ổn, tuy nhiên vẫn còn đó một số vấn đề tồn tại tử bài "Python for crawler - Part 1 - Tự động lấy dữ liệu từ trang dantri.vn/su-kien.htm".
– Code khá đơn giản, chạy hardcode 1 link
– Chạy đơn luồng, không thể áp dụng vào thực tế.
– Code theo site cần lấy, khi cần crawl từ trang khác thì sao => sửa code => không ổn.
– Lấy xong chưa biết làm gì ngoài ngắm cho vui?