Scrapy và Splash — Sự kết hợp tuyệt vời

Đỗ Anh Tú
4 min readMay 26, 2017

--

hay là cách dùng Splash với Scrapy để crawl các trang web sử dụng Javascript.

I. Javascript, chuyện không của riêng ai

Nếu đã nhảy vào lĩnh vực crawl, hẳn chẳng có ai là không vò đầu bứt tai khi phải crawl những trang sử dụng Javascript (JS). Lý do là ngày xưa, thời tốc độ Internet còn chậm, thì ít ai rảnh háng mà ngồi viết 1 trang web bằng 1 đống JS cho nặng nề, ảnh hưởng đến tốc độ truy cập của người dùng.

Chính vì thế, crawl ngày xưa khá đơn giản. Chỉ đơn thuần là bóc tách CSS, XPath, HTML etc … Nhưng hiện nay thì không đơn giản như vậy. Javascript everywhere. Ít nhiều trang nào cũng dính dáng đến JS.

II. Cách giải quyết

Chính thế, người ta sinh ra các phương pháp để crawl các trang bằng JS. Và phổ biến nhất là giả lập các browser để render đống JS đó, rồi gửi lại đống HTML đã generated về crawler để bóc tách như bình thường.

Nhưng khoan đã. Trước khi bắt đầu với giải pháp này, bạn hãy thử dùng Google Cache xem sao. Vì khả năng lớn là google đã crawl trang đó cho bạn rồi, và bạn chỉ việc crawl từ google. Vừa đỡ spam server của người ta, vừa nhanh :D Tiết kiệm RAM hơn là dùng thuần qua 1 bước dùng Splash render nữa.

Và phổ biến nhất nhì trong các công cụ, là SeleniumPhantomJS. Cá nhân mình thì lại không khoái 2 thằng này lắm. Selenium thì support rất tốt cho Python, nhưng lại nặng nề quá. PhantomJS thì thuần … JS, mà mình cũng ngại viết code JS nhiều =))

Mà cũng hên, ScrapingHub đã publish một tool mới để giải quyết vấn đề. Đó chính là Splash. Vài dòng giới thiệu Splash trên trang Github:

Splash is a javascript rendering service with an HTTP API. It’s a lightweight browser with an HTTP API, implemented in Python using Twisted and QT.

It’s fast, lightweight and state-less which makes it easy to distribute.

Tuyệt vời hơn nữa, là Splash hỗ trợ rất nhiều cho Scrapy, crawl framework chính mà mính đang dùng. Có hẳn một lib riêng, đó là scrapy-splash. Yay!

III. Cách dùng scrapy-splash

Trên trang Github của scrapy-splash cũng đã ghi khá rõ, cơ mà hơi sơ sài. Nên bạn cần phải đọc thêm docs của Splash nữa mới dùng được.

  1. Cài đặt Splash

Đầu tiên thì phải có Docker cái đã. Sau đó chỉ cần chạy:

$ sudo docker pull scrapinghub/splash

$ sudo docker run -p 8050:8050 scrapinghub/splash

là ta đã có Splash để dùng rồi.

2. Cài scrapy-splash

Bạn nên dùng virtualenv, cài scrapy và scrapy-splash bằng command

$ pip install scrapy scrapy-splash

Và thêm config trong file settings.py như sau:

# ...SPLASH_URL = 'http://localhost:8050'
DUPEFILTER_CLASS = 'scrapy_splash.SplashAwareDupeFilter'
HTTPCACHE_STORAGE = 'scrapy_splash.SplashAwareFSCacheStorage'
COOKIES_ENABLED = True # Nếu cần dùng Cookie
SPLASH_COOKIES_DEBUG = False
SPIDER_MIDDLEWARES = {
'scrapy_splash.SplashDeduplicateArgsMiddleware': 100,
}
DOWNLOADER_MIDDLEWARES = {
'scrapy_splash.SplashCookiesMiddleware': 723,
'scrapy_splash.SplashMiddleware': 725,
'scrapy.downloadermiddlewares.httpcompression.HttpCompressionMiddleware': 810,
'scrapy.downloadermiddlewares.useragent.UserAgentMiddleware': 400,
}
# ...

Xem thêm về cách config tại đây

3. Cách dùng

Có 2 cách sử dụng, một là dùng Request của Scrapy, mà cách này phức tạp và không nên dùng. Cách còn lại là dùng SplashRequest. Mình sẽ hướng dẫn dùng cách này.

Hãy nhìn đoạn crawl sau của mình:

Bởi vì Fahasa có cơ chế dùng JS để gen cookies, nên mình phải chờ để cookies được gen ra, và send cookie đó cho các request khác sử dụng. Mình sẽ cố gắng giải thích workflow của đoạn code trên.

Đầu tiên, chúng ta có list các url để crawl tại start_urls. Bình thường thì scrapy sẽ parse trực tiếp từ các url trong này, nhưng vì ở đây có 1 bước dùng JS đầu tiên nên ta không thể làm vậy được, mà phải đi qua function start_requests, dùng SplashRequest chờ 5s và update cookies đã được gen cho các request sắp tới, bằng script

script = """
function main(splash)
splash:init_cookies(splash.args.cookies)
local url = splash.args.url
assert(splash:go(url))
assert(splash:wait(5))
return {
cookies = splash:get_cookies(),
html = splash:html()
}
end
"""

Sau khi có cookies và HTML đã được render, gửi trực tiếp tới parse để lấy link các page tiếp theo và các link sách trong từng page. Vẫn tiếp tục sử dụng cookies từ response object trước, script gần giống bên trên, chỉ là giảm thời gian chờ đi vì ta đã có cookies, không cần thiết phải chờ Fahasa chạy JS để gen nữa.

script2 = """
function main(splash)
splash:init_cookies(splash.args.cookies)
local url = splash.args.url
assert(splash:go(url))
assert(splash:wait(0.5))

return {
cookies = splash:get_cookies(),
html = splash:html()
}
end
"""

Chỉ có vậy thôi, script được viết bằng LUA, vì vậy bạn nên đọc qua bài viết Học Lua trong 15 phút để biết thêm 1 ít về cú pháp. Mình sẽ dừng bài viết này ở đây, vì bài viết mang tính chất giới thiệu nhiều hơn là đi sâu vào chi tiết :D

Còn đi vào chi tiết thì bạn đọc thêm ở document của scrapy-splash nhé. Chào tạm biệt và hẹn gặp lại.

--

--