Daily Archives: 13/04/2020

Scrapy – Tạo project Scrapy P2

Trong phần này chúng ta tiếp tục code thêm một vài thứ cho project được tạo trong phần trước.

Lưu dữ liệu vào file

Trong phần trước, khi chúng ta chạy lệnh scrapy crawl basic, Scrapy sẽ cào data của trang example.com và trả về một đối tượng json. Lần này chúng ta chạy lại lệnh đó nhưng có thể quy định Scrapy lưu output vào file như sau:

D:/ex>scrapy crawl basic -o items.json
[
    {"title": ["Example Domain"]}
]

D:/ex>scrapy crawl basic -o items.jl
{"title": ["Example Domain"]}

D:/ex>scrapy crawl basic -o items.csv
<?xml version="1.0" encoding="utf-8"?>
<items>
<item><title><value>Example Domain</value></title></item>
</items>

D:/ex>scrapy crawl basic -o items.xml
title
Example Domain

Chúng ta truyền vào tham số -o và tên của file mà chúng ta muốn lưu. Lưu ý tên file phải có đuôi định dạng, nếu không Scrapy sẽ báo lỗi. Scrap có thể lưu rất nhiều loại định dạng, ở đây chúng dùng 4 loại định dạng là JSON, JSON Line, CSV và XML. Bạn có thể tìm hiểu thêm về các định dạng này trên mạng. Sau khi chạy xong bạn sẽ thấy có các file với tên mà chúng ta truyền vào được tạo ra và có nội dung như trên.

Hiện tại Scrapy không có tính năng lưu dữ liệu thẳng vào database. Tuy nhiên chúng ta sẽ tìm hiểu cách lưu vào database trong các bài sau.

Ngoài ra chúng ta còn có thể yêu cầu Scrapy upload thẳng data lên các dịch vụ đám mây thông qua FTP hay S3 như ví dụ sau:

D:/ex> scrapy crawl basic -o "ftp://user:pass@ftp.scrapybook.com/items.json"
D:/ex> scrapy crawl basic -o <b>"</b>s3://aws_key:aws_secret@scrapybook/items.json"

Lưu ý là bạn phải đặt thông tin đăng nhập các tài khoản S3 hay hosting vào thì đoạn code trên mới chạy.

ItemLoader và Processor

Trong Scrapy có một lớp tên là ItemLoader, có chức năng giúp chúng ta quản lý các đoạn code dễ dàng hơn và xử lý dữ liệu đơn giản hơn.

Chúng ta sửa lại phương thức parse() như sau:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.loader import ItemLoader
from ex.items import ExItem

class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
    start_urls = ( 'http://example.com', )

    def parse(self, response):
        ld = ItemLoader(item=ExItem(), response=response)
        ld.add_xpath('title', '//div//h1/text()')
        return ld.load_item()

Trong đoạn code trên thì chúng ta khởi tạo một đối tượng từ class ItemLoader và truyền các tham số về ExItem() và XPath vào đối tượng này bằng phương thức add_xpath(), sau đó return bằng phương thức load_item(). Khi bạn chạy scrapy crawl basic thì kết quả vẫn như cũ không có gì thay đổi.

Tuy nhiên khi dùng ItemLoader, chúng ta có thể sử dụng được các tiện ích của một nhóm class khác được gọi là Processors. Đây là các class làm các công việc hậu xử lý dữ liệu (data post-processing), chẳng hạn như tách chuỗi, nối chuỗi, parse từ kiểu này sang kiểu khác…v.v Một số lớp tiêu biểu là Join – nối chuỗi, MapCompose – chuẩn hóa và format chuỗi. Ví dụ:

Đầu tiên chúng ta sửa class ExItem trong file items.py ở thư mục trước như sau:

# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/items.html

import scrapy
from scrapy.item import Item, Field

class ExItem(scrapy.Item):
    Ex1 = Field()
    Ex2 = Field()
    Ex3 = Field()

Ở đây chúng ta chỉ thay đổi các thuộc tính. Tiếp theo chúng ta sửa file basic.py như sau:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, Join
from ex.items import ExItem

class BasicSpider(scrapy.Spider):
    name = "basic"
    allowed_domains = ["web"]
    start_urls = ( 'http://example.com', )

    def parse(self, response):
        ld = ItemLoader(item=ExItem(), response=response) 
        ld.add_xpath('Ex1', '//p', Join())		
        ld.add_xpath('Ex2', '//p[1]/text()', MapCompose(lambda i: i.replace('.', ',')))
        ld.add_xpath('Exe', '//p[1]/text()', MapCompose(str.strip))
        return ld.load_item()

Trong đoạn code trên, chúng ta truyền thêm vào phương thức add_xpath() các đối tượng Processor. Với thuộc tính Ex1, chúng ta truyền vào đối tượng Join(), đối tượng này sẽ gộp 2 element <p> lại thành 1 khi trả về. Ở thuộc tính Ex1, chúng ta truyền vào MapCompose(...), bên trong là một hàm được viết theo biểu thức lambda, có tác dụng đổi tất cả kí tự dấu chấm thành dấu phẩy. Còn ở thuộc tính Ex3 thì cũng là một đối tượng MapCompose() với tham số là str.strip, tham số này sẽ bảo MapCompose làm công việc loại bỏ kí tự trắng thừa ở trước, sau và giữa các kí tự.

[
    {
        "Ex1": ["<p>This domain is for use in illustrative examples in documents. 
               You may use this\n    domain in literature without prior coordination or 
               asking for permission.</p> 
               <p><a href=\"https://www.iana.org/domains/example\">
               More information...</a></p>"], 
        "Ex2": ["This domain is for use in illustrative examples in documents, 
               You may use this\n    domain in literature without prior coordination or 
               asking for permission,"], 
        "Ex3": ["This domain is for use in illustrative examples in documents. 
               You may use this\n    domain in literature without prior coordination or 
               asking for permission."]
    }
]

Bạn có thể tìm hiểu thêm về ItemLoader và processor tại đây:

  • http://doc.scrapy.org/en/latest/topics/loaders.html

Contracts

Contracts có tác dụng giống như Unit Test. Nếu bạn không biết Unit Test là gì thì có thể hiểu đơn giản rằng đây là tính năng cho phép chúng ta kiểm tra xem code có còn thực hiện đúng những gì nó làm hay không sau một thời gian dài không được đụng tới.

Contracts được tạo ra bằng cách đặt các luật trong phần comment sử dụng 3 dấu nháy đôi, các luật này được bắt đầu bằng kí tự @. Để ví dụ thì chúng ta sửa file basic.py như sau:

# -*- coding: utf-8 -*-
import scrapy
from scrapy.loader import ItemLoader
from scrapy.loader.processors import MapCompose, Join
from ex.items import ExItem

class BasicSpider(scrapy.Spider):    
    name = "basic"
    allowed_domains = ["web"]
    start_urls = ( 'http://example.com', )

    def parse(self, response):
        """
        @url http://example.com
        @returns items 1
        @scrapes title
        """
        ld = ItemLoader(item=ExItem(), response=response) 
        ld.add_xpath('title', '//div/h1/text()') 
        return ld.load_item()

Ý nghĩa ở đây là: Spider này sẽ cào dữ liệu ở trang web được quy định trong @url, và trả về 1 items được quy định trong @return, tên thuộc tính được trả về phải là title trong @scrapes. Phần định nghĩa contract phải được đặt phía sau tên phương thức.

Để chạy contracts thì chúng ta dùng lệnh scrapy check <tên_spider>:

D:/ex> scrapy check basic
-----------------------------------------------------------------------
Ran 2 contracts in 0.784s
OK

Nếu bạn ra được OK thì tức là code vẫn hoạt động bình thường. Nếu sai thì Scrapy sẽ in ra FAILED.