Category Archives: scrapy

Scrapy – Ngôn ngữ XPath

Nếu bạn “xuất thân” là một coder truyền thống, thì nhiều khả năng là bạn chưa biết gì về XPath, trích xuất dữ liệu từ một website là công việc đòi hỏi các kĩ năng so sánh chuỗi, tìm các thẻ, xử lí các kí tự…v.v thậm chí là xử lý một cây HTML để lấy cho được cái mà bạn cần. Nhưng thực tế thì bạn không cần làm những việc phức tạp đó. Bạn có thể làm tất cả mọi thứ đó với một ngôn ngữ tên là XPath. 

Bạn có thể vào Console của Google Chrome và thử tính năng $x của XPath. Chẳng hạn bây giờ bạn vào trang example.com và gõ đoạn code $x('//h1')  vào console và xem kết quả:

Console sẽ trả về cho bạn một mảng trong Javascript chứa các phần tử HTML h1 tương ứng đã được lấy ra.

Một số cú pháp XPath hữu dụng

Cấu trúc HTML luôn luôn bắt đầu với <html>, chúng ta có thể kết hợp tên phần tử với dấu slash / để lấy phần tử mong muốn. Ví dụ (khi chạy trên trang example.com):

$x('/html') 
Kết quả: [ html ]
$x('/html/body') Kết quả: [ body ]
$x('/html/body/div') Kết quả: [ div ]
$x('/html/body/div/h1') Kết quả: [ h1 ]
$x('/html/body/div/p') Kết quả: [ p, p ] $x('/html/body/div/p[1]') Kết quả: [ p ] $x('/html/body/div/p[2]') Kết quả: [ p ]

$x('/html/head/title')
Kết quả: [ title ]

Chú ý là bên trong element <div> có 2 element <p>, do đó cú pháp html/body/div/p trả về mảng chứa 2 phần tử <p>. Chúng ta có thể dùng có pháp p[1]p[2] để lấy ra chỉ duy nhất element <p> tương ứng.

Với những trang web lớn thông thường thì để trích xuất một element có thể phải viết đoạn XPath rất dài. Tuy nhiên chúng ta có thể chèn 2 dấu // ở đầu đoạn code để lấy tất cả các phần tử tương ứng mà không cần biết cấu trúc của chúng. Ví dụ. //p lấy tất cả element <p>, //a thì sẽ lấy tất cả element <a>.

$x('//p')
Kết quả: [ p, p ]

$x('//a')
Kết quả: [ a ]

2 dấu /X/ cũng có thể được viết ở các element phía sau. Ví dụ //div//a sẽ lấy tất cả element <a> mà nằm trong element <div>.Lưu ý là //div/a (1 dấu / trước a)  sẽ trả về mảng rỗng. Bởi vì trong trang example.com thì không có element <a> nào nằm ngay trong element <div>.

$x('//div//a')
Kết quả: [ a ]

$x('//div/a')
Kết quả: [ ]

Chúng ta cũng có thể lấy thuộc tính của element. Ví dụ:

$x('//a/@href')
Kết quả: [ href ]

Chúng ta có thể dùng dấu * để lấy tất cả các element con trong một element. Ví dụ:

$x('//div/*') 
Kết quả: [h1, p, p]

Chúng ta có thể trích xuất những element chỉ mang theo nó những attribute nhất định. Chẳng hạn @class, @id... hay những attribute nào mang một giá trị nào đó. Ví dụ //a[@href] sẽ trả về những element <a> có attribute href bên trong. Hoặc //a[@href="https://www.iana.org/domains/example"] sẽ trả về chỉ những element <a> có attribute href có giá trị là đường dẫn như trên.

$x('//a[@href="https://www.iana.org/domains/example"]')
Kết quả: [ a ]

Một kiểu cú pháp nữa là tìm các đường link có chứa một chuỗi nào đó, ví dụ:

Tìm element <a> href chứa có chuỗi iana
$x('//a[contains(@href, "iana")]')

Tìm element <a> có href bắt đầu với http://www.
$x('//a[starts-with(@href, "http://www.")]')

Tìm element <a> có href không chứa chuỗi abc
$x('//a[not(contains(@href, "abc"))]')

not,starts-with,contains()… được gọi là các hàm. ngoài 3 hàm đó thì XPath còn rất nhiều hàm khác. Nhưng chúng ta sẽ tìm hiểu thêm sau.

Dùng XPath với Scrapy

Chúng ta mở console lên và gõ scrapy shell <đường_dẫn_trang_web> để bắt đầu trích xuất dữ liệu từ trang đó. Ví dụ:

>>> scrapy shell example.com

Từ đây chúng ta có thể sử dụng nhiều loại biến toàn cục mà Scrapy cung cấp để truy xuất dữ liệu. Một trong số đó là biến response thuộc lớp HtmlResponse, lớp này có phương thức xpath() dùng để lấy dữ liệu theo cú pháp XPath. Một số ví dụ:

>>> response.xpath('/html').extract()
['<html>...</html>']
>>> response.xpath('/html/body/div/h1').extract()
['<h1>Example Domain</h1>']
>>> response.xpath('/html/body/div/p').extract()
['<p>This domain is...</p>']
>>> response.xpath('//html/head/title').extract()
['<title>Example Domain</title>']
>>> response.xpath('//a').extract()
['<a href="https://www.iana.org/domains/examples">More information...</a>']
>>> response.xpath('//a/@href').extract() 
['http://www.iana.org/domains/example'] 
>>> response.xpath('//a/text()').extract() 
['More information…']

Bạn có thể test thử code XPath trong Chrome Console, sau đó chạy thử trong Scrapy.

Dùng Chrome để lấy XPath

Chúng ta có thể vào Console của Chrome để copy đoạn XPath như trong hình dưới đây:

Xử lý những thay đổi của website

Các trang web đôi khi hay thay đổi giao diện dù ít hay nhiều, và điều đó sẽ làm cho các đoạn XPath của chúng ta bị sai. Do đó chúng ta cần liên tục cập nhật lại XPath cho đúng, thông thường việc này cũng không tốn nhiều thời gian. Tuy nhiên có một số tip mà chúng ta nên để ý để tránh kéo dài thời gian thay đổi:

  • Không hard-code số thứ tự trong mảng. Ví dụ nếu chúng ta lấy XPath từ Chrome thì thường là Chrome sẽ trả về rất nhiều chỉ số mảng như:
//*[@id="myid"]/div/div/div[1]/div[2]/div/div[1]/div[1]/a/img
  • Và chỉ cần trang web thêm một thẻ <div> vào đầu là chúng ta phải cập nhật lại hết các chỉ số trên. Để giải quyết việc này thì chúng ta nên lấy XPath càng gần với thẻ <img> cuối càng tốt. Ví dụ:
//div[@class="thumbnail"]/a/img
  • Tên class hướng dữ liệu sẽ có ích hơn các class hướng giao diện. Chẳng hạn như nhắm đến class thumbnail sẽ tốt hơn là nhắm đến class green,  nhưng chúng vẫn kém hiệu quả hơn class như container. Hai class đầu tiên chủ yếu dùng để tùy chỉnh giao diện web, trong khi cái cuối cùng thì lại có í nghĩa về mặt thiết kế hơn, và do đó ít bị thay đổi hơn.
  • ID thường là thứ đáng tin cậy nhất nên tốt nhất là thường xuyên sử dụng chúng để trích xuất dữ liệu. Ví dụ:
//*[@id="more_info"]//text()

Lưu ý là tránh các id do website tự tạo ra. Chẳng hạn //[@id=”order-F498232″] vì chúng không cố định.

Scrapy – Bản chất của HTML

Để có thể trích xuất thông tin từ các trang web thì chúng ta cần phải hiểu sâu một tí về cấu trúc của chúng. Nếu bạn đã từng làm lập trình web thì bạn có thể bỏ qua phần này vì trong phần này chúng ta sẽ tìm hiểu nhanh về các thành phần của HTML và cấu trúc dạng cây của nó.

Khi một người dùng duyệt web, đầu tiên họ phải gõ đoạn đường dẫn URL lên trình duyệt (hoặc click vào một đường link nào đó) và đợi trang web đó hiện lên. Chúng ta có thể hình dung quá trình này gồm 4 bước:

  • Đầu tiên đường dẫn được gõ vào trình duyệt. Các thông tin như tên miền (chẳng hạn phocode.com) – là thứ dùng để định vị máy chủ lưu trữ trang web đó, cookie, các form…v.v là các dữ liệu sẽ được gửi tới máy chủ đó.
  • Tiếp theo máy chủ sẽ trả lời lại bằng cách gửi về một “trang” HTML cho trình duyệt. Ngoài ra máy chủ còn có thể gửi về các dạng dữ liệu khác như XML hay JSON. Trong series này thì chúng ta chủ yếu làm việc với HTML thôi.
  • Đoạn văn bản HTML sẽ được chuyển đổi sang một kiểu cấu trúc dạng cây và được lưu trong trình duyệt. Tên của nó là Document Object Model (DOM).
  • Trình duyệt sẽ dựa trên “cây” HTML đó để hiển thị tất cả mọi thứ lên màn hình cho chúng ta xem.

Bây giờ chúng ta sẽ tìm hiểu kĩ từng bước đó.

Đường dẫn – URL

Trong khuôn khổ series này thì chúng ta chỉ cần biết URL có 2 phần. Phần đầu tiên giúp chúng ta định vị máy chủ của trang web thông qua các dịch vụ phân giải tên miền – Domain Name System (DNS). Chẳng hạn như khi chúng ta gõ https://phocode.com/python/scrapy/, thì các máy chủ DNS sẽ dịch đoạn tên miền đó ra địa chỉ IP là 128.199.120.120. Và đoạn URL lúc này sẽ là http://128.199.120.120/python/scrapy. Phần còn lại trong đoạn URL là thứ dành cho máy chủ web, máy chủ sẽ dựa vào đó mà trả lại đúng trang HTML tương ứng. Đó có thể là một trang tin tức, một tấm ảnh, hoặc mở email…v.v

File HTML

Máy chủ sẽ đọc đoạn URL, phân tích xem người dùng yêu cầu cái gì và gửi lại một đoạn văn bản HTML. Đoạn văn bản này là một file text mà bạn có thể mở ra đọc bằng các trình soạn văn bản như Notepad, Microsoft Word, vim… Tuy nhiên đoạn văn bản này được viết theo cấu trúc chuẩn được quy định bởi hiệp hội web toàn cầu – World Wide Web Consortium (W3C). Chúng ta sẽ chỉ tìm hiểu sơ chứ không đi sâu vào tiêu chuẩn này. Nếu bạn gõ vào http://example.com, bạn sẽ thấy đoạn mã HTML do máy chủ trả về bằng cách xem mã nguồn trang web (xem trên Chrome bằng cách bấm Ctrl-U) như sau:

 
<!doctype html>
  <html>
    <head>
      <title>Example Domain</title>
      <meta charset="utf-8" />
      <meta http-equiv="Content-type" content="text/html; charset=utf-8" />          
      <meta name="viewport" content="width=device-width, initial-scale=1" /> 
      <style type="text/css">              
        body {                  
          background-color: ...              
      }
      </style>
    </head>
  <body>
    <div>
      <h1>Example Domain</h1>
      <p>This domain is established to be used for illustrative examples examples in documents. You may use this domain in examples without prior coordination or asking for permission.</p>
      <p><a href="http://www.iana.org/domains/example">More information</a></p>          
    </div>     
  </body>
</html>

Thông thường một đoạn HTML có thể chỉ nằm trên một dòng duy nhất, đoạn code ở trên mình đã viết lại cho dễ đọc. Trong HTML thì khoảng trống và xuống dòng không thật sự có nhiều công dụng. Những từ nằm trong cặp dấu <> được gọi là các thẻ, chẳng hạn <html> hay <head>. Thẻ có thêm dấu / được gọi là thẻ đóng (vd </html).

Điều này có nghĩa là các thẻ luôn đi cùng nhau, có thẻ “mở” thì phải có thẻ “đóng”. Tuy nhiên đôi khi cũng có các ngoại lệ, nhiều khi chúng ta sẽ thấy có thẻ mở nhưng không hề có thẻ đóng đi sau, tuy nhiên website vẫn hiển thị bình thường, trình duyệt sẽ tự động tìm cách đặt thẻ đóng thay thế.

Những gì bên trong một cặp thẻ đóng-mở được gọi là một phần tử HTML. Một phần tử HTML có thể chứa các phần tử HTML khác. Một số thẻ thì cao cấp hơn một tí, chẳng hạn như <a href="...">, trong đó href là một thuộc tính của thẻ.

Và các thẻ có thể chứa đoạn văn bản nào đó, chẳng hạn “Example Domain” nằm trong cặp thẻ <h1></h1>.
Chúng ta không cần phải hiểu rõ toàn bộ mọi thứ trong HTML. Thứ chúng ta cần biết đó là những thứ nằm trong cặp thẻ <body></body>, còn những thứ còn lại như <head></head> không quan trọng vì chúng chỉ cung cấp thông tin thêm cho trình duyệt và Scrapy hầu như cũng sẽ tự lo phần đó cho chúng ta.

Cấu trúc cây

Mỗi trình duyệt có cách lưu trữ cấu trúc dữ liệu khác nhau và do đó trang web hiển thị lên cũng không giống nhau (một cách hoàn toàn). Nhưng hầu hết các trình duyệt đều hỗ trợ DOM.
Chúng ta có thể xem mã nguồn và thậm chí tìm xem phần tử nào phụ trách phần nào trên màn hình bằng cách bấm F12 khi dùng Chrome (hoặc click chuột phải và chọn Inspect Element).
Bạn có thể thấy nó khá giống một đoạn code HTML, nhưng thực chất nó không hẳn là một đoạn text HTML mà là một “cây” HTML. Bạn có thể trỏ con chuột vào từng phần tử trong cây này và thấy phần nào được tô đậm trên trang web, bạn còn có thể chỉnh sửa các phần tử này như thay đổi, thêm, xóa phần tử…v.v.
Nói chung bạn cần biết rằng HTML chỉ là một đoạn văn bản (mà bạn có thể gõ trong Office Word). Còn thứ nằm trong trình duyệt là một cấu trúc dữ liệu dạng cây biểu thị cho đoạn HTML đó và được lưu trong bộ nhớ của trình duyệt và chúng ta có thể chỉnh sửa trực tiếp.


Scrapy – Giới thiệu

Scrapy là một web framework rất mạnh mẽ trong việc trích xuất dữ liệu. Scrapy có kiến trúc sự kiện, cho phép chúng ta thực hiện các công việc như dọn dẹp, tạo, lưu trữ, làm giàu dữ diệu…v.v

Scrapy đã hiện diện từ năm 2008, một số tính năng của scrapy như sau:

  • Có thể làm việc với các đoạn code HTML “xấu”
  • Cộng đồng lớn
  • Mã nguồn ổn định
  • Tốc độ xử lí nhanh theo dạng bất đồng bộ
  • Có thể lưu dữ liệu theo nhiều định dag như JSON, CSV, XML.
  • Có thể trích xuất dữ liệu bằng cách sử dụng các biểu thức XPath hay CSS

Cài đặt

Để có thể cài đặt và sử dụng Scrapy thì bạn cần có Python 3 trong máy. Để cài đặt Scrapy thì chúng ta dùng trình pip:

pip install scrapy

Lưu ý là bạn có thể phải cài thêm Visual C++ Build Tools thì pip mới có thể cài Scrapy được.

Kiểm tra phiên bản

Chạy lệnh scrapy version để xem phiên bản scrapy mà chúng ta đang vừa cài:

$ scrapy version
Scrapy 1.8.0

Serie này sử dụng phiên bản 1.8.0