Lấy hình từ Flickr với JavaScript

Cách đây vài hôm, ông anh họ gặp tôi và nhờ giúp ổng một chuyện. Số là thế này, ổng tìm được một tài khoản Flickr chứa đầy những tấm hình ổng thích (xin đừng hỏi tôi là hình gì). Ổng muốn tải toàn bộ hình về máy. Tuy nhiên, tài khoản đó có hơn 10.000 tấm, tải về theo cách thủ công là điều không tưởng. Do vậy, ổng tìm tôi để nhờ giúp đỡ. Sau một giờ mày mò trong cái API Documentation của Flickr, tôi đã tìm ra cách để chôm toàn bộ hình từ tài khoản Flickr bất kỳ. Đặc biệt, ứng dụng chỉ dùng JavaScript.

Lấy API Key từ Flickr

Trước khi viết ứng dụng này, ta phải lấy API Key từ Flickr. Đây là một chuỗi các ký tự giúp Flickr quản lý việc sử dụng API. Để lấy API Key, ta đăng nhập vào Flickr, sau đó, vào menu Explore rồi chọn App Garden. Phía bên phải, ta sẽ thấy mục Get an API Key, và trong trang tiếp theo, ta chọn phần Non-Commercial Key để lấy key dùng cho mục đích phi thương mại.

Lưu ý rằng đây là chìa khóa để truy cập vào dịch vụ API của Flickr, bất kỳ ai có key này đều có thể sử dụng API từ tài khoản của bạn. Do đó, bạn phải luôn giữ kín cái key này.

Tạo trang HTML

Tiếp theo, ta sẽ tiến hành tạo trang HTML. Trang này chứa một form rất đơn giản, chỉ bao gồm một ô nhập liệu để nhập số ID tài khoản Flickr và một nút bấm để gửi request. Sau đây là toàn bộ code trong thẻ <body> của trang HTML:

1 <h1>Get photo links from Flickr | Hiếu Sensei</h1>
2 <p><input type="text" id="user" placeholder="Flickr ID" />
3 <input type="button" id="get" value="Get links" /></p>
4 <p id="status"></p>
5 <div id="result"></div>

Bây giờ ta sẽ chuyển qua phần quan trọng nhất: viết JavaScript cho ứng dụng.

Viết JavaScript

Đầu tiên, ta cần thêm jQuery vào trang. Tôi sẽ tận dụng hàm gửi Ajax request và xử lý JSON của jQuery. Kế đến, tôi tạo file script.js và cũng thêm vào trang.

1 <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
2 <script src="script.js"></script>

Tiếp theo, tôi sẽ lần lượt khai báo biến tham số để dùng gửi request lên Flickr. Ta có thể tham khảo về những tham số này trong Documentation.

1 var url = "https://api.flickr.com/services/rest/";
2 var key = "asryf546rt5dsg856df32gdr5yhdf";
3 var perPage = 500;
4 var currentPage = 1;
5 var totalPhotos;
6 var totalPages;
7 var extras = "url_m, url_n, url_z, url_c, url_l, url_o";
8 var reqParams;
9 var interval;

Chức năng của các biến như sau:

  • Dòng 1 là biến chứa đường link để truy cập dịch vụ API của Flickr.
  • Dòng 2 là API Key mà Flickr cung cấp.
  • Dòng 3 xác định số hình trong một trang. Mỗi request chỉ được lấy tối đa 500 hình.
  • Dòng 4 là biến theo dõi trang hiện tại. Từ đó, ta có thể đến trang tiếp theo bằng cách tăng biến này lên.
  • Dòng 5 khai báo biến totalPhotos để lưu trữ tổng số hình trong tài khoản.
  • Dòng 6 giúp lưu lại tổng số trang hiện có.
  • Dòng 7 là những tham số phụ nhằm báo cho Flickr biết kích thước hình ta muốn lấy. Ở đây tôi muốn lấy tất cả kích thước, từ trung bình (medium) cho đến kích thước gốc (original). Thông thường, khi tải hình, ta chỉ muốn download ảnh có kích cỡ gốc. Tuy nhiên, có những trường hợp một tấm ảnh không có đủ kích thước ta muốn. Vì vậy, tôi lấy về toàn bộ kích thước để kiểm tra từ từ, nếu kích thước lớn nhất không tồn tại, tôi sẽ lấy kích thức lớn thứ 2, và cứ như thế cho đến kích thước trung bình.
  • Dòng 8 tôi khai báo biến để chứa toàn bộ các tham số. Thực ra, ta không cần biến này nhưng tôi muốn mọi thứ rõ ràng và gọn gàng.
  • Dòng 9, tôi khai báo biến interval. Lý do tôi tạo biến này sẽ được giải thích sau.

Kế tiếp, tôi sẽ viết các hàm cần thiết để phục vụ mục đích lấy hình. Hàm đầu tiên ta cần viết là hàm getUserPhotos(). Nghe tên chắc ai cũng có thể đoán ra mục đích chính của hàm này là gì rồi.

 1 function getUserPhotos(user) {
 2     reqParams = {
 3             "api_key": key,
 4             "method": "flickr.people.getPublicPhotos",
 5             "user_id": user,
 6             "extras": extras,
 7             "per_page": perPage,
 8             "page": currentPage,
 9             "format": "json",
10             "nojsoncallback": 1
11         };
12     $.getJSON(url, reqParams, callBack);
13 }

Trong hàm này, tôi sẽ gán một object vào trong biến reqParams, và sau đó truyền nó vào hàm getJSON() của jQuery ở dòng 13. Các thuộc tính trong đối tượng này ta có thể tham khảo trong Documentation. Hàm getJSON() của jQuery cần 3 tham số. Thứ nhất đó là đường dẫn đến dịch vụ API, ở đây tôi đã lưu nó vào biến url nên giờ chỉ cần truyền nó vào. Tham số thứ hai là đối tượng chứa các thông số kèm theo request, ở đây tôi truyền vào biến reqParams. Cuối cùng, tham số thứ ba là tên hàm sẽ chạy khi nhận được response trả về từ phía server. Phần này không có gì phức tạp, nếu bạn quen dùng tính năng Ajax của jQuery thì đoạn code trên chỉ là chuyện nhỏ.

Tiếp theo ta sẽ viết một hàm callBack() để nó chạy tự động khi nhận response từ server.

1 function callBack(data, status) {
2     var photoObj = data.photos;
3     totalPhotos = photoObj.total;
4     totalPages = photoObj.pages;
5     listPhotos(photoObj.photo);
6 }

Hàm callBack() có 2 tham số. Tham số data chứa dữ liệu trả về từ server. Còn tham số status chứa trạng thái của request. Nếu có lỗi xảy ra, ta sẽ được thông báo trong biến status. Trong phần thân hàm, tôi tiến hành mổ xẻ nội dung của biến data. Để xem nội dung biến này, ta có thể dùng câu lệnh console.log(data) để hiển thị nội dung trong console. Dựa vào những nội dung khám phá ra trong cửa sổ console, tôi biết được những dữ liệu cần lấy ra nằm ở vị trí nào, và trong phần thân hàm callBack(), tôi lần lượt lấy nó ra và đưa vào biến thích hợp mà tôi đã khai báo lúc đầu. Ở dòng 2, tôi lưu đối tượng photo vào biến photoObj. Kế đến, tôi lần lượt lấy ra thông tin về tổng số hình (total) và tổng số trang (pages) và gán nó vào biến phù hợp. Cuối cùng, tôi gọi hàm listPhotos() có nhiệm vụ hiển thị danh sách các link hình vừa nhận được. Tôi truyền vào trong hàm này một mảng chứa các đối tượng hình. Sau đó, trong phần thân hàm listPhotos(), tôi sẽ trích ra từng đường link đến tấm hình tương ứng.

Tiếp theo, ta sẽ viết hàm listPhotos() như đã đề cập ở trên. Cụ thể như sau:

 1 function listPhotos(photoArray) {    
 2     if (currentPage <= totalPages) {
 3         var directLink = "";
 4         var link = "";
 5         var title = "";
 6         var output = "";
 7         
 8         for (var i = 0; i < photoArray.length; i++) {
 9             directLink = getLargestSize(photoArray[i]);
10             link = "<a href='" + directLink + "' target='_blank'>" + directLink + "</a>";
11             title = photoArray[i].title ? " - <span>" + photoArray[i].title + "</span>" : "";
12             
13             output += link + title + "<br>";
14         }
15 
16         $("#result").html($("#result").html() + output);
17         currentPage++;
18     } else {
19         clearInterval(interval);
20         $("#status").html("There are " + totalPhotos + " photos from the account:");
21     }
22 }

Có thể nói, đây là hàm phức tạp nhất trong tất cả các hàm của ứng dụng này. Đầu tiên, tôi sẽ tính coi phải gửi bao nhiêu request đến server. Như đã đề cập ở đầu bài, Flickr chỉ cho phép trả về tối đa 500 hình trong một request. Do đó, nếu muốn lấy nhiều hơn 500 hình, ta phải gửi nhiều request. Nếu nhạy bén, ta sẽ phát hiện ra số request cần gửi tương ứng với tổng số trang chứa hình, mỗi trang chứa 500 tấm. Do đó, tôi sẽ gửi request tương ứng với số trang, có bao nhiêu trang thì sẽ có bấy nhiêu request. Tôi kiểm tra điều kiện này trong câu lệnh if ở dòng 2.

Kế đến, tôi tạo ra vài biến để chứa các thông tin mà tôi cần lấy ra khỏi đống bùi nhùi mà Flickr trả về dưới dạng object. Tôi dùng vòng lặp for để lặp qua toàn bộ phần tử trong mảng photoArray chứa tất cả hình trong một trang và trích ra đường dẫn trực tiếp đến chúng. Lưu ý ở đây tôi dùng thêm một hàm phụ có tên là getLargestSize() để lấy ra ảnh có kích thước lớn nhất. Tuy nhiên, không phải tấm hình nào cũng có tất cả các kích thước. Vì vậy, hàm getLargestSize() sẽ giúp lấy ra hình có kích cỡ lớn nhất có thể. Tôi lưu đường link trực tiếp tới hình vào biến directLink. Ngay bên dưới, tôi định dạng đường link bằng cách cho nó vào thẻ <a> rồi gán vào trong biến link.

Ngoài hiển thị link, tôi muốn hiển thị luôn tiêu đề của bức ảnh. Tuy nhiên, không phải tấm hình nào cũng có tiêu đề nên tôi phải kiểm tra trước. Nếu có thì hiển thị ra như ở dòng 11. Cuối cùng, tôi gom mọi thứ lại và nhét vào trong biến output để lát nữa chèn vào thẻ <div>idresult (dòng 16). Và tôi cũng không quên tăng số trang hiện tại lên 1 đơn vị (dòng 17) để lần sau chạy nó sẽ lấy hình ở trang tiếp theo. Trong phần else, nếu trang hiện tại currentPage lớn hơn tổng số trang totalPages, nghĩa là đã lấy xong toàn bộ hình, tôi sẽ bỏ interval đi (tôi sẽ giải thích cái interval này ở bên dưới). Sau đó tôi đưa thông báo ra màn hình ở dòng 20.

Hàm tiếp theo mà ta sẽ viết là getLargestSize(). Mục đích của hàm này khá đơn giản, đó là tìm ra kích thước hình lớn nhất và trả về đường link. Nếu không tìm được thì nó sẽ tìm tiếp đường link tới kích thước lớn tiếp theo và cứ tiếp tục như thế.

1 function getLargestSize(photo) {
2     var output = photo.url_o;
3     if (output === undefined) output = photo.url_l;
4     if (output === undefined) output = photo.url_c;
5     if (output === undefined) output = photo.url_z;
6     if (output === undefined) output = photo.url_n;
7     if (output === undefined) output = photo.url_m;
8     return output;
9 }

Hàm getLargestSize() chỉ bao gồm các câu lệnh if để kiểm tra điều kiện rồi trả về kết quả. Tiếp theo, ta sẽ viết hàm run() làm điểm khởi đầu để chạy toàn bộ quy trình lấy hình.

1 function run() {
2     var user = $("#user").val();
3     getUserPhotos(user);
4 }

Đầu tiên, ta lấy giá trị nhập vào của người dùng. Ở đây, giá trị chính là mã số ID của tài khoản Flickr. Sau đó, tôi gọi hàm getUserPhotos() để tiến hành gửi request. Phần khó khăn nhất đã hoàn thành, giờ ta còn phải làm một bước cuối cùng đó là tạo hàm xử lý sự kiện click cho nút bấm có idget.

1 $("#get").click(function () {
2     $("#status").html("Loading...");
3     $("#result").html("");
4     currentPage = 1;
5     interval = setInterval(run, 3000);
6 });

Nội dung của hàm này khá rõ rồi nên tôi không cần giải thích gì thêm. Tuy nhiên, có một điểm nhỏ mà tôi muốn đề cập là vấn đề dùng setInterval() để chạy request sau mỗi 3 giây. Lý do chính mà tôi làm việc này là để tránh tình trạng lạm dụng (abuse) server của Flickr, khiến nó khóa tài khoản của tôi. Đây cũng là điều mà ta nên suy xét kỹ khi sử dụng bất kỳ dịch vụ API nào. Gửi request quá nhiều lần trong một khoảng thời gian ngắn là điều cấm kị, và nhà cung cấp API sẽ không vui khi ta làm điều đó. Cho nên tốt nhất là ta kéo dài khoảng thời gian gửi request, hoặc cũng có thể cho chạy random để giả lập hành vi người dùng, tránh mọi nghi ngờ từ phía nhà cung cấp API. Bạn cũng đừng quên coi kỹ các chính sách và yêu cầu khi sử dụng dịch vụ. Tốt nhất là ta nên “chấp hành nghiêm chỉnh đường lối” của Flickr để tránh tai họa về sau.

Vậy là ứng dụng chôm hình từ tài khoản Flickr đã xong, ta có thể chia sẻ ứng dụng này cho người khác dùng. Tuy nhiên, cần lưu ý một điều là cái API Key được lưu trong file JavaScript và nó có thể được truy cập bởi bất kỳ ai. Do đó, tôi khuyên bạn không nên công khai cái ứng dụng này trên mạng, tránh trường hợp bị kẻ gian lợi dụng cái API Key và hậu quả là Flickr khóa tài khoản của bạn. Ông anh họ của tôi khá hài lòng với cái ứng dụng này. Trong máy ổng có cài Internet Download Manager, chỉ cần click phải chuột và chọn Download all links with IDM là có thể tải về toàn bộ ảnh. Ổng khen phương pháp này nhanh, hiệu quả mà không phức tạp, rất phù hợp cho những người mù tin học như ổng. Kết quả là tôi được một chầu cà phê miễn phí. Đời developer thật là sang!