Web scraping với GPT-4o: mạnh mẽ nhưng tốn kém
(blancas.io)- Đã thử nghiệm một trình web scraper có AI hỗ trợ để trích xuất bảng HTML thành dữ liệu có cấu trúc bằng structured outputs của OpenAI API, và GPT-4o cho chất lượng trích xuất tốt hơn kỳ vọng ở một số bảng phức tạp
- Với những bảng có cấu trúc hiển thị phức tạp như dự báo 10 ngày của Weather.com, mô hình phân biệt được dự báo ban ngày/ban đêm, đồng thời tìm được cả những giá trị không hiển thị trên màn hình nhưng có trong mã nguồn HTML
- Với các bảng có hàng gộp chứa giá trị lặp lại như Human Development Index trên Wikipedia, số lượng giá trị theo từng cột bị lệch nhau nên khó tái dựng bảng, và việc chỉ chỉnh prompt không giải quyết được
- Đã thử cả tạo XPath để giảm chi phí gọi API, nhưng XPath tạo ra có lúc sai hoặc trả về kết quả rỗng; cách ổn định hơn là trích xuất dữ liệu trước rồi yêu cầu XPath dựa trên các giá trị đó
- Đã chi $24 trong hai ngày thử nghiệm; sau khi giảm bớt thuộc tính HTML thì số ký tự giảm một nửa và chưa thấy suy giảm hiệu năng, nhưng việc kiểm chứng thêm bị hạn chế vì chi phí
Trích xuất bảng HTML bằng structured outputs
- Đã triển khai một web scraper có AI hỗ trợ bằng tính năng structured outputs của OpenAI API
- Thử nghiệm đầu tiên là truyền chuỗi HTML vào GPT-4o và yêu cầu nó trích xuất dữ liệu bảng dưới dạng có cấu trúc
- Mô hình Pydantic được dùng gồm
ParsedTableđể chứa tên bảng và danh sách cột, cùng cấu trúcParsedColumnđể chứa tên cột và danh sách giá trị - Trong system prompt, mô hình được giao vai trò một trình web scraper chuyên trích xuất dữ liệu có cấu trúc từ bảng HTML
- Cũng đã thử GPT-4o mini nhưng kết quả kém hơn nhiều, nên các thử nghiệm sau đó được tiến hành với GPT-4o
Điểm mạnh thể hiện ở các bảng phức tạp
- Sau các bảng đơn giản, tác giả đưa vào bảng dự báo thời tiết 10 ngày của Weather.com
- Bảng này có một hàng lớn ở trên cùng, còn 9 ngày còn lại được tạo thành bởi các hàng nhỏ hơn
- GPT-4o nhận ra cấu trúc dự báo ngày/đêm của 9 ngày còn lại và thêm cột
Day/Night - Ban đầu cột
Conditionđược trích xuất trông giống như hallucination, nhưng sau khi kiểm tra mã nguồn HTML thì hóa ra nó thực sự tồn tại dưới dạng thẻ không hiển thị trên màn hình
Thất bại với các hàng gộp
- Bảng Human Development Index trên Wikipedia nhìn bề ngoài khá đơn giản, nhưng do các hàng có giá trị lặp lại bị gộp, GPT-4o không xử lý đúng
- Mô hình đã trích xuất từng cột theo đúng chỉ dẫn, nhưng số lượng giá trị ở mỗi cột khác nhau nên khó biểu diễn kết quả thành bảng
- Tác giả đã đổi system prompt theo kiểu “với hàng gộp thì hãy trích xuất thành nhiều giá trị JSON để số hàng của mọi cột bằng nhau”, nhưng không hiệu quả
- Prompt yêu cầu trích xuất theo từng hàng thay vì theo cột thì vẫn chưa được thử
Giới hạn của cách tạo XPath
- Vì gọi OpenAI API mỗi lần đều có thể làm chi phí tăng cao, tác giả thử yêu cầu trả về XPath thay cho dữ liệu đã trích xuất
- Mục tiêu là khi cần lấy lại dữ liệu đã cập nhật từ cùng một trang, có thể scrape bằng XPath mà không cần gọi lại mô hình
- Prompt được thiết kế để nhận nội dung HTML và tên cột, rồi trả về chuỗi XPath có thể được đánh giá trong
driver.find_elements(By.XPATH, xpath)của Selenium - Cách này không ổn định
- Có trường hợp GPT-4o trả về XPath sai
- Việc nhắc đến Selenium trong prompt giúp cải thiện phần nào, nhưng vẫn xuất hiện XPath trả về dữ liệu sai hoặc kết quả rỗng
Kết hợp trích xuất dữ liệu và tạo XPath
- Thử nghiệm tiếp theo là để GPT-4o trích xuất dữ liệu trước, rồi dùng kết quả đó làm giá trị tham chiếu để yêu cầu XPath
- Cách kết hợp này hoạt động tốt hơn nhiều so với việc yêu cầu XPath ngay từ đầu
- Có trường hợp XPath được tạo ra không trả về kết quả, nên tác giả thêm một logic thử lại đơn giản: nếu không có kết quả thì chạy lại
- Với các bảng đã thử, cách retry này có hiệu quả
- Tuy nhiên vẫn còn vấn đề mới
- Ở bước đầu, việc trích xuất dữ liệu đôi khi biến hình ảnh thành văn bản
- Ví dụ, ảnh mũi tên hướng lên có thể bị trích xuất thành văn bản như
arrow-upwards - Khi đó, bước thứ hai sẽ cố tìm văn bản không tồn tại thật trong HTML và bị lỗi
- Vấn đề này chưa được xử lý riêng
Chi phí và dọn dẹp HTML
- Khi dùng GPT-4o cho web scraping, ngay cả bảng HTML nhỏ cũng có thể chứa rất nhiều ký tự nên chi phí tăng cao
- Trong hai ngày thử nghiệm đã chi $24
- Để giảm chi phí, tác giả thêm logic dọn dẹp nhằm loại bỏ dữ liệu không cần thiết khỏi chuỗi HTML trước khi gửi cho mô hình
- Sau khi loại bỏ mọi thuộc tính trừ
class,id,data-testid— những thứ thường được XPath tạo ra sử dụng — số ký tự của bảng giảm còn một nửa - Sau bước dọn dẹp này chưa thấy suy giảm hiệu năng, và tác giả cho rằng chất lượng trích xuất thậm chí có thể được cải thiện
- Hiện tại, giai đoạn tạo XPath thực hiện một lần gọi mô hình cho mỗi cột của bảng
- Cũng có thể cải tiến để tạo nhiều XPath cùng lúc, nhưng tác giả chưa thử hoặc đánh giá hiệu năng của cách đó
Demo và ý tưởng thử nghiệm thêm
- Chất lượng trích xuất của GPT-4o rất ấn tượng, nhưng chi phí phải trả cho OpenAI cũng rất đáng kể
- Có thể xem bản demo đơn giản được làm bằng Streamlit tại https://orange-resonance-9766.ploomberapp.io
- Mã nguồn được công khai trên GitHub, kèm lưu ý rằng đây không phải một công cụ hoàn thiện cao
- Tác giả muốn thử trên nhiều bảng hơn, nhưng do chi phí OpenAI tăng lên nên chỉ thử được với số ít bảng
- Nếu có thêm thời gian, tác giả dự định thử những hướng sau
- Demo hiện tại là một quy trình dùng một lần, nơi người dùng nhập URL và XPath ban đầu; trải nghiệm người dùng có thể tốt hơn nếu để người dùng nhấp vào bảng cần trích xuất và cung cấp một hàng mẫu
- Với các bảng phức tạp, có thể khó trích xuất cả cột chỉ bằng một XPath duy nhất, nên tác giả muốn thử cách yêu cầu LLM trả về một chương trình như Python
- Vì chi phí dùng GPT-4o đắt và cảm giác đang gửi đi quá nhiều dữ liệu không cần thiết, tác giả cho rằng cần thử thêm các cách dọn dẹp HTML
1 bình luận
Ý kiến trên Hacker News
Thay vì đưa HTML trực tiếp vào LLM, kết quả tốt nhất đạt được khi trước tiên chuyển nó sang định dạng đơn giản như Markdown
Các công cụ đã dùng gồm Extractus https://github.com/extractus và dom-to-semantic-markdown https://github.com/romansky/dom-to-semantic-markdown
Bên trong Magic Loops https://magicloops.dev/ chạy trên cloud, họ dùng Apify https://apify.com/ và Firecrawl https://www.firecrawl.dev/, còn trên Chrome Extension thì dùng dom-to-semantic-markdown
Hiện tại họ đang thử nghiệm cách tạo XPath cho từng website cụ thể như một luồng hỗ trợ người dùng, rồi trích ra chỉ những phần tử cần thiết trước khi gọi LLM; khi đơn giản hóa bài toán như vậy thì ngay cả GPT-4o mini cũng cho kết quả khá ổn
Nó có thể chạy Chrome Extension trong trình duyệt headless, nên có thể chuyển đổi sang Markdown ngữ nghĩa ngay trong trình duyệt trước khi lấy dữ liệu ra ngoài
Ngoài ra còn có màn hình trực tiếp theo thời gian thực của trình duyệt có thể nhúng bằng iFrame, nên người dùng có thể nhận phản hồi ngay về XPath đang được tạo: https://docs.browserbase.com/features/session-live-view#give...
Theo kinh nghiệm của tôi, hiệu năng khá tương đương với Markdown, nhưng việc chuyển đổi dễ hơn và có ít trường hợp ngoại lệ hơn
Trang tôi tìm thấy là “Alpha Draft”, nên có lẽ chưa có nhiều kho ngữ liệu nội dung Semantic Markdown, và việc thiếu dữ liệu huấn luyện có thể cản trở khả năng hiểu của LLM
Dù vậy, đây vẫn là định dạng khá dễ đọc, nên nếu LLM xử lý metadata có cấu trúc như phần mô tả trong ngoặc thì có lẽ vẫn xử lý khá tốt
OpenAI gần đây công bố Batch API, cho phép chuẩn bị toàn bộ prompt rồi chạy hàng loạt, đồng thời giảm chi phí xuống còn 50%: https://platform.openai.com/docs/guides/batch
Trước đây tôi đã dùng nhiều với GPT-4o mini và có thể xử lý 3.000 mục trong chưa đến 5 phút
Đây có thể là lựa chọn rất tốt cho các ứng dụng không cần thời gian thực
vLLM đã bổ sung một phần hỗ trợ “offline batch mode” cùng kiểu, nhưng vẫn chưa đi đến mức triển khai endpoint OpenAI
Nó là một tính năng quá tự nhiên
Với nội dung có cấu trúc, ví dụ như danh sách mục hoặc bảng đơn giản, thực ra không cần đến LLM
Gần đây tôi đã tạo một web scraper https://easyscraper.com có thể tự động hoạt động trên bất kỳ website nào; phiên bản đầu tiên dùng AI, nhưng cuối cùng các heuristic dựa trên thuộc tính phần tử và vị trí lại nhanh hơn, rẻ hơn và chính xác hơn
Trên phần lớn website, cách tiếp cận không dùng AI hoạt động rất tốt, nên trước hết nên kiểm tra xem AI có thực sự cần thiết hay không, chẳng hạn trong các trường hợp dữ liệu phi cấu trúc hoặc cần suy ra định dạng đầu ra dựa trên dữ liệu của trang
Giống như tác giả bài viết, có thể để LLM tạo XPath, rồi bình thường thì scrape theo XPath đó, còn khi bị hỏng thì để LLM cập nhật lại XPath
Dù vậy, nếu dữ liệu vẫn không chảy lại hoặc bị lỗi ở phần sau của pipeline do định dạng bất ngờ, thì lúc đó chỉ cần cảnh báo cho con người
Tôi luôn thắc mắc vì sao lại chưa có công cụ như vậy
Tò mò không biết đã có thứ gì như "HTML reducer" chưa
Nếu nhìn nguyên mã nguồn trang thì 90% token là rác do JavaScript ngẫu nhiên, quảng cáo, thuộc tính không cần thiết và lồng nhau quá mức để phục vụ render
Nếu duyệt bằng DOM parser và chỉ giữ lại các node có văn bản, cấu trúc HTML và các thuộc tính thẻ cần thiết (
class/idchẳng hạn), có vẻ có thể giảm chi phí đáng kể, và cách XPath cũng có thể hoạt động tốt hơnTôi cũng dùng Readability, nhưng nó làm mất cấu trúc DOM, và với các website nhiều JavaScript hoặc các trang mở rộng văn bản như “continue reading” thì chất lượng giảm đi
Tò mò không biết công cụ nào gần như là tiêu chuẩn cho mục đích này
Nếu có quan tâm thì có thể open source nó, và thật bất ngờ là sau khi qua reducer, đầu ra của LLM tốt hơn hẳn
Chỉ cần thêm https://r.jina.ai/ vào trước bất kỳ URL nào, nó sẽ trả về phiên bản Markdown của nội dung chính trên trang đó, phù hợp để đưa vào LLM
Ví dụ là https://r.jina.ai/https://simonwillison.net/2024/Sep/2/anato... và trang gốc là https://simonwillison.net/2024/Sep/2/anatomy-of-a-textual-us...
Mã nguồn là open source nên có thể tự chạy: https://github.com/jina-ai/reader
Nó được viết bằng TypeScript và dùng Puppeteer cùng https://github.com/mozilla/readability
Tôi thì chỉ dùng Readability mà không chuyển sang Markdown để trích xuất tiêu đề và nội dung trang, và cũng có recipe chạy bằng Playwright với shot-scraper: https://shot-scraper.datasette.io/en/stable/javascript.html#...
Cách này được lấy từ vimium rồi chỉnh sửa lại, chưng cất HTML để chỉ giữ phần quan trọng và cũng xử lý nhiều trường hợp ngoại lệ
Khá ngạc nhiên là tác giả đã làm xong 99% công việc và phần viết lách, nhưng lại không tải thử ollama hay engine dựa trên llama.cpp, tức 1% còn lại, để thử một local LLM ổn ổn
Với use case này, cả model 7B hay 30B cũng có thể làm tốt, và chi phí chạy cũng đủ thấp để có thể không cần GPT-4o
Giờ tôi mới bắt đầu vọc LLM và cũng định sẽ xem thử các model self-hosted
Kadoa https://kadoa.com đã làm mảng web scraping tự động bằng AI, và các thử nghiệm ban đầu cũng giống bài viết
Bọn tôi bắt đầu từ thời chỉ có GPT-3 vừa đắt vừa chậm, nên cần một lời giải hiệu quả về chi phí ở quy mô lớn
Cuối cùng, thay vì dùng LLM ở mọi bước trích xuất, bọn tôi dùng sinh mã để tạo code trích xuất CSS selector hoặc XPath, rồi sau đó để nó điều chỉnh code scraper theo thay đổi của website
Với bước làm sạch và chuyển đổi thì dùng các LLM nhỏ đã fine-tune, còn phần kiểm chứng thì đánh giá chất lượng dữ liệu bằng LLM-as-a-judge cùng với các phương pháp truyền thống như reverse search
Việc áp dụng cho vài nguồn dữ liệu đơn giản và việc chạy ổn định, có khả năng mở rộng, tiết kiệm chi phí trên hàng nghìn website là hai bài toán hoàn toàn khác nhau, và cách đúng là kết hợp ETL engineering truyền thống với các bước LLM nhỏ, được đánh giá kỹ
Nếu đưa cho nó một ví dụ HTML muốn scrape và yêu cầu đoạn mã BeautifulSoup, kết quả khá tốt
Thông thường cấu trúc cần lấy vẫn được giữ nguyên, nhưng tự tay viết các chuỗi khó hiểu để parse nó thì rất chán
Giao luôn việc parse thực tế cho LLM thì hơi quá tay, lại còn có nguy cơ kết quả bị nhiễm hallucination
Nếu tiền xử lý HTML trước thì có thể rẻ hơn và cho kết quả tốt hơn
Hình như chưa thấy ai nhắc đến, nhưng cá nhân tôi có kết quả rất tốt với trafilatura https://trafilatura.readthedocs.io/en/latest/
Với LLM thì chỉ cần gửi văn bản nên có thể cắt giảm chi phí rất mạnh
Tôi cũng đã dùng nó trong dự án gần đây https://github.com/philippe2803/contentmap, một thư viện Python đơn giản để tạo vector store cho một website bất kỳ, lấy XML sitemap của domain làm điểm khởi đầu
Vì cấu trúc HTML của mỗi domain khác nhau nên cần trích đúng phần nội dung thực tế và loại bỏ thẻ HTML các thứ, và Trafilatura làm được việc đó cho gần như mọi URL chỉ với vài dòng code
Đây là lần đầu tôi thấy một bài viết gắn nhiều thẻ màu cam đến vậy
Tại NewsCatcher, họ đang thử nghiệm rất nhiều với GPT-4o và phải crawl hơn 100.000 website tin tức rồi parse nội dung tin bài
Mô hình dựa trên luật để trích xuất dữ liệu từ bất kỳ bài viết nào cũng hoạt động khá tốt, và họ chưa tìm ra cách nào dùng GPT để cải thiện thêm
Điều thú vị hơn là ở phía crawl: phải biết mọi vị trí mà bài báo có thể được đăng, và đôi khi có hơn 50 tiểu mục
Vì cấu trúc của từng website không thay đổi thường xuyên, họ cho rằng với nhiều dự án, chỉ cần tạo mã trích xuất là đủ
Vì vậy họ đang xem xét hướng dùng LLM để sinh mã parse HTML, ai quan tâm có thể liên hệ artem [at] newscatcherapi.com
Giá mà có tự đăng ký thì tốt
Web scraping chính là lý do thực sự khiến tôi và đồng sáng lập tạo ra openpipe.ai hiện tại
GPT-4 làm việc này cực kỳ tốt nhưng quá đắt
Tuy vậy, khả năng scrape một số loại website cụ thể lại khá dễ để chưng cất sang các mô hình fine-tune rẻ hơn nhiều, và trên những loại website đó chúng scrape ổn định, đáng tin cậy
Bọn tôi cũng đã làm bài toán này khá lâu rồi, tôi sẽ liên hệ để cho anh thấy bọn tôi đã đi đến đâu