Đây là bài tutorial thứ 2 trên blog. Hiện nay, nhu cầu thu thập dữ liệu ngày càng tăng. Với một số trang như lớn như facebook, google, steam ta có thể sử dụng API do họ cung cấp để lấy dữ liệu. Trong nhiều trường hợp khác, ta thường trích xuất dự liệu bằng tay (Mở trang web lên, copy dữ liệu vào file word, excel v…v), việc này vừa cực, vừa mất nhiều thời gian và công sức
Đặt tình huống cụ thể, bạn muốn làm một ứng dụng đọc báo, lấy thông tin từ chuyên mục “Đọc báo giùm bạn” trên webtretho.com. Đây là một trang forum khá to, và dĩ nhiên là không có API để lấy dữ liệu. Ở đây, ta không thể lấy dữ liệu bằng tay được. Giải pháp duy nhất cho chuyện này là viết một phần mềm trích xuất dữ liệu từ bản thân trang webtretho.
Mình sẽ hướng dẫn các bạn trích xuất bằng thư viện HTMLAgilityPack và Fizzler. HTMLAgilityPack là một thư viện parse HTML khá mạnh, lý do nó phổ biến là vì nó “chơi” được với hầu hết html, cả valid và unvalid (Trong thực tế thì số lượng website có HTML unvalid nhiều vô số kể, các thư viện khác sẽ dễ bị lỗi, HTMLAgilityPack thì không). Kiến thức ở bài này sẽ khá hữu dụng nếu sau này bạn cần trích xuất thông tin từ website khác. Bạn có thể google thêm với từ khóa: web crawler.
Cùng bắt tay vào làm nhé.
Bước 1: Tạo project mới , ở đây mình tạo một project console cho đơn giản.
Bước 2: Vào Tools -> Library Package Manager -> Package Manager Console. Đánh câu lệnh sau để cài đặt thư viện:
Install-Package Fizzler.Systems.HtmlAgilityPack
Sau khi cài đặt, nếu bạn thấy có đủ 3 reference như hình dưới là ok nhé.
Bước 3: Xem xét HTML của trang cần trích xuất. Ở đây, chúng ta sẽ trích xuất tên, link tới các topic trong diễn đàn, cũng như lấy số lượng view của topic đó.
Sử dụng Developer Tool của chrome, ta sẽ thấy mỗi topic là 1 tag li, nằm trong 1 tag ul, có id là “threads.” Ta sẽ trích xuất dữ liệu từ những thẻ này. (Kinh nghiệm của mình là bạn nên chọn tag dựa theo id, nằm gần dữ liệu mình cần lấy nhất. Vì mỗi tag trong html chỉ có 1 id duy nhất, không bị trùng, ta dễ chọn tag và lọc hơn).
Vào sâu hơn, ta sẽ thấy link và tiêu đề được lưu trữ trong taga, có class là title, còn số lần đọc được lưu trữ trong tag b. Với những thông tin này, chúng ta đã có thể bắt đầu trích xuất dữ liệu
Bước 4: Bắt đầu parse dữ liệu. Trước khi viết code, mình xin giới thiệu 1 số object, method của HTML AgilityPack mà các bạn nên biết:
–HTMLDocument: Đây là một class chứ thông tin về một file html (encoding, innerhtml). Ta có thể load dữ liệu vào HTMLDocument từ 1 URL hoặc từ 1 file. Trong bài này mình sẽ load từ url của webtretho.
–HTMLNode: Một HTMLNode tương đương với một tag (li, ul, div, …) trong HTML. Node lớn nhất chứa toàn bộ tất cả sẽ là DocumentNode. Một số property của HTMLNode mà ta hay sử dụng:
- Name: Tên của node (div, ul, li).
- Attributes: Danh sách các attribute của note (Attribute là các thông tin của node như: src, href, id, class …)
- InnerHTML, OuterHTML: Đọc tên là hiểu rồi nhỉ
- SelectNodes(string xPath): Tìm các node con của node hiện hành, dựa trên xPath đưa vào.
- SelectSingleNode(string xPath): Tìm node con đầu tiên của node hiện hành, dựa trên xPath đưa vào.
- Descendants(string xPath): Trả ra danh sách các HTMLNode con của node hiện tại.
Đầu tiên, chúng ta sẽ sử dụng method SelectNode, sử dụng xPath để tìm node. Nếu bạn không rành, không nhớ hoặc không biết xPath thì đừng lo, phía dưới sẽ có cách khác dễ hơn.
HtmlWeb htmlWeb = new HtmlWeb() { AutoDetectEncoding = false, OverrideEncoding = Encoding.UTF8 //Set UTF8 để hiển thị tiếng Việt }; //Load trang web, nạp html vào document HtmlDocument document = htmlWeb.Load("http://www.webtretho.com/forum/f26/"); //Load các tag li trong tag ul var threadItems = document.DocumentNode.SelectNodes("//ul[@id='threads']/li").ToList(); var items = new List<object>(); foreach (var item in threadItems) { //Extract các giá trị từ các tag con của tag li var linkNode = item.SelectSingleNode(".//a[contains(@class,'title')]"); var link = linkNode.Attributes["href"].Value; var text = linkNode.InnerText; var readCount = item.SelectSingleNode(".//div[@class='folTypPost']/ul/li/b").InnerText; items.Add(new { text, readCount, link }); }
Kết quả thu được khá là ưng ý:
Nhiều bạn sẽ cảm thấy cách này hơi khó. Thú thật là mình cũng không rành xPath cho lắm, vì vậy mình cũng không khoái cách này, do đó chúng ta có thể dùng LINQ to Object để tìm note. Code tương tự sẽ được viết như sau:
var threadItems = document.DocumentNode.Descendants("ul") .First(node => node.Attributes.Contains("id") && node.Attributes["id"].Value == "threads") .ChildNodes.Where(node => node.Name == "li").ToList(); foreach (var item in threadItems) { var linkNode = item.Descendants("a").First(node => node.Attributes.Contains("class") && node.Attributes["class"].Value.Contains("title")); var link = linkNode.Attributes["href"].Value; var text = linkNode.InnerText; var readCount = item.Descendants("b").First().InnerText; items.Add(new { text, readCount, link }); }
Chắc bạn hơi thật vọng phải không. Code bây giờ đã dễ hiểu hơn, không cần xPath xPiếc gì. Tuy nhiên, code lại dài hơn, do ta phải dùng lambda expression và check null. Do một số node không có attribute class, ta phải check null trước để tránh bị lỗi NullPointerException. Còn cách nào hay hơn không nhỉ? Hãy kéo xuống dưới nhé, mình luôn để dành phần hay nhất ở dưới cùng.
Chú ý: Nếu bạn bỏ vế check null ra sau, hàm chạy sẽ bị lỗi, để hiểu nguyên nhân hãy xem lại bài viết về short-circuit của mình nhé.
Bước 5: Cải tiến với Fizzler
Cả 2 cách trên đều làm bạn “đầu váng, mắt hoa” ? Ok, mình cũng vậy :(. May mắn thay, còn một cách đơn giản hơn để select 1 node, đó là sử dụng Fizzler. Fizzler hỗ trợ CSS selector, cho phép ta sử dụng selector của CSS. Fizzler được mở rộng dựa trên HTMLAgilityPath, thêm 2 hàm sau vào HTMLNode:
+ QuerySelectorAll: Tìm các node con của node hiện hành, dựa trên css selector đưa vào.
+ QuerySelector: Tìm node con đầu tiền của node hiện hành, dựa trên css selector đưa vào.
Code bây giờ vô cùng đơn giản và dễ hiểu nhé
var threadItems = document.DocumentNode.QuerySelectorAll("ul#threads > li").ToList(); foreach (var item in threadItems) { var linkNode = item.QuerySelector("a.title"); var link = linkNode.Attributes["href"].Value; var text = linkNode.InnerText; var readCount = item.QuerySelector("div.folTypPost > ul > li > b").InnerText; objs.Add(new { link, text, readCount }); }
Bước 6: Xuất kết quả ra đâu đó. Tới đây mọi chuyện đã xong, bạn có thể lưu kết quả vào file database hoặc xuất ra file text tùy mục đích sử dụng.
Bạn cũng có thể áp dụng thư viện này để trích xuất 1 số trang web bán hàng: hotdeal, muachung, … Hi vọng bài viết này sẽ có ích cho sự nghiệp lập trình của các bạn.
Cổ súy hành động chôm chất xám của người khác, reported
LikeLike
Chào bạn. Mình có ý kiến thế này.
Khi bạn gọi anh ấy là “Cổ súy hành động chôm chất xám của người khác”.
Vậy bạn cho mình xin nguồn gốc của bài viết này được không? 🙂
LikeLike
Mình nghĩ bạn “ongnoi.thienha” đang nói đến việc lấy dữ liệu từ các trang khác hơn là việc copy bài hướng dẫn từ nơi khác về đăng lên blog. Mình thì thấy việc lấy dữ liệu tự động như vậy rất có lợi, dữ liệu trên mạng rất nhiều, đôi khi phải sử dụng để xử lý, không phải cứ lấy dữ liệu xuống là chôm chất xám.
LikeLike
Đúng là ông nội thiên hạ
LikeLiked by 1 person
Chào bạn. Bài viết rất hay. Thứ lỗi cho mình, mình mới lập trình nên muốn hỏi một chút.
Với đối tường items trên, sau khi đã lấy thông tin từ trang web rồi, làm thế nào để đọc ra để console.write hoặc ghi ra file được(csv, excel). Vì các đối tượng là không được xác đinh các tên properties của nó từ trước (đến đoạn items.Add(new { text, readCount, link }); mới có new { text, readCount, link }). Bởi vì mình thử là foreach (var item in items) { Console.Write(item.readCount);} thì không được, báo lỗi là readCount chưa được xác định.
Cảm ơn bạn trước
LikeLike
Bạn tạo một class có các trường Text, ReadCount v…v rồi in ra như thường nhé
LikeLike
anh ơi em mới học lập trình, e chưa hiểu mình dùng cách nào để kết nối tới trang web để trích xuất ạ
LikeLike
a nào có thể giúp e trích xuất 1 trang web. như một sản phẩm laptop chẳng hạn.
LikeLike
hiện mình đang cần trích xuất dữ liệu từ 1 trang web ra bản ex mình không biết lập trình nêu bạn nào làm dc alo m qua số 0943255823 giá cả thỏa thuận 2 bên hợp đồng đàng hoàng cảm ơn các bạn quan tâm
LikeLike
Chào Admin
Mình đang trích xuất dữ liệu từ http://trangvangvietnam.com/categories/204010/cao_su_san_pham_cao_su.html
Nhưng sau khi chạy thử thì mình bị trường hợp như sau:
– node trên mỗi trang là 35. Khi chạy cũng ra 35 record nhưng dữ liệu chỉ lấy của node 1
Sau đây là đoạn code của mình Nhờ admin xem giúp
try
{
string url = txtUrl.Text;
HtmlAgilityPack.HtmlDocument document = new HtmlAgilityPack.HtmlDocument();
HtmlWeb web = new HtmlWeb();
document = web.Load(url);
//Lấy 1 node chứa danh sách chính
HtmlAgilityPack.HtmlNode _nodThreads = document.DocumentNode.SelectSingleNode(“//div[@class=\”listingsearch\”]”);
//Lấy danh sách nod tr có trong _nodThreads
HtmlAgilityPack.HtmlNodeCollection nodChuDe = _nodThreads.SelectNodes(“//div[@class=\”boxlistings\”]”);
foreach (var n in nodChuDe)
{
//Trích xuất các dữ liệu cần
string number = n.SelectSingleNode(“//div[@class=’number_list’]”).InnerText; //lấy text bên trong
string weburl = n.SelectSingleNode(“//div[@class=’web2section’]”).InnerText; //lấy text bên trong
dataGridView1.Rows.Add(new string[] { number, weburl });
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
LikeLike
hi bạn, mình đang tìm hiểu về html-aglitity-pack nhưng không hiểu lắm, bạn có thể cho mình xin code của bạn được không ?
LikeLike
bài viết hay đấy, nhưng cho mình hỏi là với những trang web có bút Load more thì xử lý như thế nào vậy bạn?
LikeLike
Thường nút load more đó sẽ kèm link dẫn tới trang chứa các sản phẩm còn lại để mình tiếp tục crawl.
Nếu như chỉ load more gọi hàm Ajax thì lúc đó bạn phải dùng Selenium để mở trình duyệt, load JavaScript rồi lấy HTML ra Parse, lúc này HTML Agility Pack bó tay ;))
LikeLiked by 1 person
a có thể “code dạo” một function cho phép deep crawl đc ko a :v
LikeLike
Bạn có thể lấy dữ liệu giúp mình từ 1 web vào excell được ko
LikeLike
E đang cần tạo một service có chức năng như sau với input là các image mẫu và e muốn khi m đưa các hình ảnh khác của người đó vào thì API này có nhận dạng được không a. Những công nghệ nào cần sử dụng trong th này vậy ạ . E cảm ơn.
LikeLike
Ko nhận djang được đâu em 😀
LikeLike
Mình có triển khai vụ này khá nhiều và sử dụng dịch vụ của http://www.80legs.com/
Rất nhanh và tiện 😉
LikeLike
nếu mình muốn lấy dữ liệu của 1 từ trên trang từ điển vdict thì làm thế nào ạ ?
LikeLike
a ơi cho e hỏi ví dụ e muốn lấy toàn bộ dữ liệu của một từ tiếng anh trên trang vdict.com thì làm như thế nào ạ khi mỗi từ lại có nhiều list khác nữa ?
LikeLiked by 2 people
Ví dụ em muốn lấy tất cả tin tức của mục thời sự trên trang vnexpress thì dữ liệu nó được phân ví dụ có 100 trang từ 1 đến 100 trang thì làm sao để mình có thể nhận biết và get dữ liệu của từng trang như vậy và khi get xong đến trang cuối thì nó có thể tự dừng lại được vậy anh Hoàng.
LikeLike
Hi anh. Em đang craw link phim của web. hiện tại em đang gặp vấn đề là HttpWebRequest không lấy được link của trang phim. Em đang thử các control khác như httpweb link vẫn không được trả về. sau khi check thì link lưu trong header. không biết dùng cách nào để get link phim ở jwplayer nhỉ
LikeLike
chào anh, đường link dùng để load vào trang web có cần điều kiện như thế nào không ạ. Khi em load một số trang web vào như http://www.baomoi.com/ thì chương trình báo lỗi ạ.
LikeLike
Của mình add cả 3 thư viện vào, lúc chạy nó báo lỗi sau:
Could not load file or assembly ‘Fizzler, Version=1.1.21209.0, Culture=neutral, PublicKeyToken=4ebff4844e382110’ or one of its dependencies. The located assembly’s manifest definition does not match the assembly reference. (Exception from HRESULT: 0x80131040)
Mình đang dùng VS2017 xây dựng dứng dụng trên .NET 4.6.2
LikeLike
Lỗi bày sửa ntn ạ. Làm phiền Ad hỗ trợ
LikeLike
Lỗi này mình chưa gặp nên cũng ko biết bạn ơi
LikeLike
chào bạn cho mình hỏi 1 chút.
1) nếu trang gốc thay đổi nội dung thì cái trang của mình cũng thay đổi theo không hay chỉ tại thời điểm parse.
2) có phải tất cả web đều cho phép làm điều này ? hay chỉ một số
Cảm ơn b!
LikeLike
Ý kiến của mình thế này:
– Khi trang gốc thay đổi cấu trúc html, có thể code của bạn có thể sẽ không hoạt động đúng nữa.
– Hầu hết các web đều có thể dùng được, có thể dùng kết hợp với Selenium để load html rồi xử lý.
LikeLike
Hi tiền bối… bài viết của tiền bối khá hấp dẫn. Tiền bối cho em hỏi. Em muốn lấy 1 lúc 5 trang web nhưng các thông tin css và js lại khác nhau. .. Làm sao có thể chỉ sử dụng 1 thông tin để lấy được cả 5 trang web nhỉ. em đang tìm hiểu là get detail không cần sử dụng element.
em hiện tại mới chỉ get được title của page còn phần còn lại em không lấy được.
LikeLike
Thanks in advance
Mọi người cho em hỏi, em muốn lấy dữ liệu css, javascript thì làm như thế nào?
LikeLike
Cho mình hỏi trang https://playerduo.com/ sao mình cào k đc ạ, có phải là do có “x-xss-protection: 1; mode=block”, nhưng khi mình cào từ trang https://www.topitworks.com/vi cũng có “x-xss-protection: 1; mode=block” thì lại cào đc.
LikeLike
Vì ngta render bằng JavaScript bạn nha
LikeLike
Vậy có cách nào để lấy không ạ
LikeLike
bạn dùng Selenium hoặc Pupetteer nhé 😀
LikeLike
anh ơi bây giờ em muốn lấy dữ liệu từ nhiều đường dẫn khác nhau thì hướng làm sẽ như thế nào ạ
LikeLike
mình đang muốn viết bằng python để get 10 cái link đầu tiên của google search result thì dùng thư viện gì nhỉ. có thể cho mình 1 keyword k. 😦
LikeLike
Cảm ơn tác giả đã chia sẽ kiến thức bổ ích. Với 1 người mới học lập trình thì rất khát kiến thức về mọi mặt. Cần lắm những chia sẽ về công nghệ như này để xoá mù chữ cho new member
LikeLiked by 1 person