Scrape (trích xuất) dữ liệu tự động với Scrapy

Trong 1 bài trước đây, mình đã nói qua về scrape dữ liệu bằng PHP và regular expression. Vừa rồi mình có thực hiện scrape data từ VNExpress giùm 1 người bạn, xài thử Scrapy và thấy cũng khá hiệu quả nên chia sẻ với mọi người

Ưu điểm của Scrapy là cung cấp sẵn 1 cấu trúc tương đối hoàn chỉnh để thực hiện việc crawl và scrape data, người dùng chỉ cần bổ sung thêm định nghĩa về dữ liệu cần lấy là xong (ví dụ như URL bắt đầu là gì, link chuyển qua trang mới là gì, các thông tin cần lấy ở mỗi trang là gì)

1. Sau khi cài Scrapy và các thư viện liên quan, chạy lệnh sau

scrapy startproject tutorial

Scrapy sẽ tạo 1 project hoàn chỉnh

2. Các file quan trọng

a) items.py - chứa định nghĩa thông tin mình muốn extract


from scrapy.item import Item, Field

class VNExpressItem(Item):
    # define the fields for your item here like:
    title = Field()
    url = Field()
    content = Field()
    author = Field()
    writtenOn = Field()
    tags = Field()
    catid = Field()
    postid = Field()

b) file spiders (trong thư mục spiders), định nghĩa cách chuyển giữa các trang và cách lấy thông tin. ví dụ định nghĩa domain thực hiện việc scrape


name = "vnexpress"
allowed_domains = ["vnexpress.net"]
start_urls = [
    "http://m.vnexpress.net/thegioi/the-gioi/1001002/p1/2411014/0"
]

Ở đây có 1 điểm cần chú ý là mình scrape phiên bản mobile (http://m.vnexpress.net)  thay vì bản full. Lí do là bản mobile thì dung lượng cần tải về gọn hơn và cấu trúc thì nhẹ hơn và ít thay đổi hơn bản bình thường.

Định nghĩa đường dẫn qua trang tiếp theo, dùng XPath


next_page = hxs.select("//a[@class='right txt_1_1em']/@href").extract()

đoạn ở trên sẽ tương ứng với phần HTML sau

xử lí từng post


for post in posts:
    postTitle = post.select('.//h2[@class="h2SdTopHome txt_1_5em"][1]/text()').extract()

    itemFullURL = base_url + post.select('.//@href').extract()[0]

    request = Request(itemFullURL,callback=self.parse_full_post)
    request.meta['title'] = postTitle
    request.meta['url'] = itemFullURL

    yield request

Ở đây, itemFullURL là URL của từng bài báo. Scrapy sẽ tạo 1 request mới để đọc URL này, đồng thời trả về phần callback là 1 function (parse_full_post) nhằm tiếp tục thực hiện việc trích xuất dữ liệu trong bài báo kia

đây là function parse_full_post

def parse_full_post(self, response):
    fullPost = HtmlXPathSelector(response)

    post_content = fullPost.select('.//div[@class="fck_detail pNormalD fontSizeCss left"][1]').extract()
    post_time = fullPost.select('.//div[@class="art_time left"][1]').extract()
    post_tags = fullPost.select('.//meta[@name="keywords"][1]/@content').extract()
    post_author = fullPost.select('.//p[@class="Normal"][last()]').extract()
    cat_id = fullPost.select('.//meta[@name="tt_category_id"][1]/@content').extract()
    post_id = fullPost.select('.//meta[@name="tt_article_id"][1]/@content').extract()

    item = VNExpressItem()
    item['title'] = response.meta['title']
    item['url'] = response.meta['url']
    item['content'] = post_content
    item['writtenOn'] = post_time
    item['tags'] = post_tags
    item['author'] = post_author
    item['catid'] = cat_id
    item['postid'] = post_id

    yield item

ở đây ta thực hiện việc lấy các dữ liệu cần thiết (title, content, writtenOn v.v....)

nếu đến đây thì bạn đã có thể chạy Scrapy và cho xuất dữ liệu ra theo dạng CSV hay JSON bằng cú pháp sau

scrapy crawl VNExpress -o output.json -t json

c) nếu muốn insert các kết quả tìm được vào database thì sử dụng tiếp phần pipeline (file pipelines.py)

def process_item(self, item, spider):
    try:
        self.cursor.execute("""INSERT INTO article_stub (title, url, content, post_time, tags, author, catid, postid) VALUES (%s, %s, %s, %s, %s, %s, %s, %s)""",
    (
        item['title'][0].encode('utf-8'), #item['title'].encode('utf-8'),
        item['url'].encode('utf-8'), #item['url'].encode('utf-8')
        item['content'][0].encode('utf-8'),
        item['writtenOn'][0].encode('utf-8'),
        item['tags'][0].encode('utf-8'),
        item['author'][0].encode('utf-8'),
        item['catid'][0].encode('utf-8'),
        item['postid'][0].encode('utf-8')
    )
    )

    self.conn.commit()

    return item

phần chính trong pipeline là function process_item, function này sẽ chạy cho mỗi đơn vị thông tin (trong bài viết này thì là bài báo) được trích xuất

3. Tóm tắt:

Đây là sourcecode của script dùng để scrape VNExpress, mình chạy script này scrape toàn bộ bài viết trong 1 chuyên mục (Xã hội), bao gồm khoảng 50,000 bài báo mất khoảng 6 tiếng