Chuyển đến nội dung chính

Yêu cầu HTTP (+ làm việc với cookie, proxy, phiên)

Base class methods

To collect data from a web page, you need to make an HTTP request. In JavaScript API v2 of A-Parser, a lightweight method for making HTTP requests is implemented, which returns a JSON object in response depending on the specified method arguments. Next, you will learn: how an HTTP request is made, what arguments and options the method has, the results of the specified options, how to specify the success condition of an HTTP request, and more. These methods are inherited from BaseParser and are the basis for creating your own parsers Getting an HTTP response for a request, the following arguments are specified:

  • method - request method (GET, POST...)

  • url - link for the request

  • queryParams - hash with get parameters or hash with the body of a post request HTTP request hoặc lưu để thực thi bởi luồng khác bằng Trình quản lý phiên.

Các phương thức này được kế thừa từ BaseParser và là nền tảng để tạo các công cụ cào dữ liệu tùy chỉnh

await this.request(method, url[, queryParams][, opts])

await this.request(method, url, queryParams, opts)

Nhận phản hồi HTTP theo truy vấn, các tham số được chỉ định như sau:

  • method - phương thức của truy vấn (GET, POST...)
  • url - liên kết cho truy vấn
  • queryParams - hash với tham số get hoặc hash với thân của truy vấn post
  • opts - hash với các tùy chọn của truy vấn

opts.check_content

check_content: [ điều kiện1, điều kiện2, ...] - mảng điều kiện để kiểm tra nội dung nhận được, nếu kiểm tra không đạt, thì truy vấn sẽ được lặp lại với proxy khác.

Tính năng:

  • sử dụng chuỗi làm điều kiện (tìm theo sự xuất hiện của chuỗi)
  • sử dụng biểu thức chính quy làm điều kiện
  • sử dụng các hàm kiểm tra riêng, trong đó dữ liệu và header của phản hồi được truyền vào
  • có thể đặt nhiều loại điều kiện khác nhau cùng lúc
  • để phủ định logic, hãy đặt điều kiện vào mảng, tức là check_content: ['xxxx', [/yyyy/]] có nghĩa là truy vấn sẽ được coi là thành công nếu trong dữ liệu nhận được có chuỗi con xxxx và đồng thời biểu thức /yyyy/ không tìm thấy kết quả khớp trên trang

Để truy vấn thành công, tất cả các kiểm tra được chỉ định trong mảng phải đạt

Ví dụ (trong phần chú thích có ghi những gì cần để truy vấn được coi là thành công):

let response = await this.request('GET', set.query, {}, {
check_content: [
/<\/html>|<\/body>/, // trên trang nhận được phải khớp với biểu thức chính quy này
['XXXX'], // trên trang nhận được không được có chuỗi con này
'</html>', // trên trang nhận được phải có chuỗi con này
(data, hdr) => {
return hdr.Status == 200 && data.length > 100;
} // hàm này phải trả về true
]
});

opts.decode

decode: 'auto-html' - tự động xác định mã hóa và chuyển đổi sang utf8

Các giá trị có thể có:

  • auto-html - dựa trên header, thẻ meta và nội dung của trang (phương án khuyến nghị tối ưu)
  • utf8 - chỉ định rằng tài liệu ở mã hóa utf8
  • <encoding> - bất kỳ mã hóa nào khác

opts.headers

headers: { ... } - hash với header, tên header được đặt ở dạng chữ thường, cũng có thể chỉ định cookie.

Ví dụ:

headers: {
accept: 'image/avif,image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8',
'accept-encoding': 'gzip, deflate, br',
cookie: 'a=321; b=test',
'user-agent' 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.150 Safari/537.36'
}

opts.headers_order

headers_order: ['cookie', 'user-agent', ...] - cho phép ghi đè thứ tự sắp xếp của header

opts.onlyheaders

onlyheaders: 0 - xác định việc đọc data, nếu bật (1), chỉ nhận header

opts.recurse

recurse: N - số lần chuyển hướng tối đa, mặc định là 7, dùng 0 để tắt việc đi theo chuyển hướng

opts.proxyretries

proxyretries: N - số lần thử thực hiện truy vấn, mặc định lấy từ cài đặt của công cụ cào dữ liệu

opts.parsecodes

parsecodes: { ... } - danh sách các mã phản hồi HTTP mà công cụ cào dữ liệu sẽ coi là thành công, mặc định lấy từ cài đặt của công cụ cào dữ liệu. Nếu chỉ định '*': 1 thì tất cả phản hồi sẽ được coi là thành công.

Ví dụ:

parsecodes: {
200: 1,
403: 1,
500: 1
}

opts.timeout

timeout: N - thời gian chờ phản hồi tính bằng giây, mặc định lấy từ cài đặt của công cụ cào dữ liệu

opts.do_gzip

do_gzip: 1 - xác định có sử dụng nén hay không (gzip/deflate/br), mặc định bật (1), để tắt cần đặt giá trị 0

opts.max_size

max_size: N - kích thước tối đa của phản hồi tính bằng byte, mặc định lấy từ cài đặt của công cụ cào dữ liệu

opts.cookie_jar

cookie_jar: { ... } - hash với cookie. Ví dụ hash:

"cookie_jar": {
"version": 1,
".google.com": {
"/": {
"login": {
"value": "true"
},
"lang": {
"value": "ru-RU"
}
}
},
".test.google.com": {
"/": {
"id": {
"value": 155643
}
}
}

opts.attempt

attempt: N - chỉ định số lần thử hiện tại, khi sử dụng tham số này trình xử lý thử tích hợp cho truy vấn này sẽ bị bỏ qua

opts.browser

browser: 1 - mô phỏng tự động header của trình duyệt (1 - bật, 0 - tắt)

opts.use_proxy

use_proxy: 1 - ghi đè việc sử dụng proxy cho một truy vấn riêng bên trong JS parser trên tham số toàn cục Use proxy (1 - bật, 0 - tắt)

opts.noextraquery

noextraquery: 0 - tắt việc thêm Extra query string vào URL của truy vấn (1 - bật, 0 - tắt)

opts.save_to_file

save_to_file: file - cho phép tải file trực tiếp xuống đĩa, bỏ qua việc ghi vào bộ nhớ. Thay vì file cần chỉ định tên và đường dẫn để lưu file theo cách nào. Khi dùng tùy chọn này, mọi thứ liên quan đến data sẽ bị bỏ qua (kiểm tra nội dung trong opts.check_content sẽ không được thực hiện, response.data sẽ trống, v.v.)

opts.bypass_cloudflare

bypass_cloudflare: 0 - tự động vượt qua bảo vệ JavaScript của CloudFlare bằng trình duyệt Chrome (1 - bật, 0 - tắt)

Việc kiểm soát Chrome Headless trong trường hợp này được thực hiện bởi các cài đặt của công cụ cào dữ liệu bypassCloudFlareChromeMaxPagesbypassCloudFlareChromeHeadless, cần chỉ định trong static defaultConfstatic editableConf:

static defaultConf: typeof BaseParser.defaultConf = {
version: '0.0.1',
results: {
flat: [
['title', 'Title'],
]
},
max_size: 2 * 1024 * 1024,
parsecodes: {
200: 1,
},
results_format: "$title\n",
bypass_cloudflare: 1,
bypassCloudFlareChromeMaxPages: 20,
bypassCloudFlareChromeHeadless: 0
};

static editableConf: typeof BaseParser.editableConf = [
['bypass_cloudflare', ['textfield', 'bypass_cloudflare']],
['bypassCloudFlareChromeMaxPages', ['textfield', 'bypassCloudFlareChromeMaxPages']],
['bypassCloudFlareChromeHeadless', ['textfield', 'bypassCloudFlareChromeHeadless']],
];

async parse(set, results) {
const {success, data, headers} = await this.request('GET', set.query, {}, {
bypass_cloudflare: this.conf.bypass_cloudflare
});
return results;
}

opts.follow_meta_refresh

follow_meta_refresh: 0 - cho phép đi theo các chuyển hướng được khai báo qua thẻ meta HTML:

<meta http-equiv="refresh" content="time; url=..."/>

opts.redirect_filter

redirect_filter: (hdr) => 1 | 0 - cho phép đặt hàm lọc việc đi theo chuyển hướng, nếu hàm trả về 1, thì công cụ cào dữ liệu sẽ đi theo chuyển hướng (tính đến tham số opts.recurse), khi trả về 0 việc đi theo chuyển hướng sẽ dừng lại:

redirect_filter: (hdr) => {
if (hdr.location.match(/login/))
return 1;
return 0;
}

opts.follow_common_rediects

opts.follow_common_rediects: 0 - xác định có đi theo các redirect chuẩn hay không (ví dụ http -> https và/hoặc www.domain.com -> domain.com), nếu đặt 1 thì công cụ cào dữ liệu sẽ đi theo các redirect chuẩn mà không tính tham số opts.recurse

opts.http2

opts.http2: 0 - xác định có sử dụng giao thức HTTP/2 khi thực hiện truy vấn hay không, mặc định sử dụng HTTP/1.1

opts.randomize_tls_fingerprint

opts.randomize_tls_fingerprint: 0 - tùy chọn này cho phép vượt qua chặn website theo dấu vân tay TLS (1 - bật, 0 - tắt)

opts.tlsOpts

tlsOpts: { ... } – cho phép truyền các cài đặt cho kết nối https ​

await this.cookies.*

Làm việc với cookies cho truy vấn hiện tại

.getAll()

Lấy mảng cookies

await this.cookies.getAll();
Ví dụ về kết quả lấy mảng cookies

.setAll(cookie_jar)

Thiết lập cookies, đối số phải được truyền vào là hash với cookie

async parse(set, results) {
this.logger.put("Start scraping query: " + set.query);

await this.cookies.setAll({
"version": 1,
".google.com": {
"/": {
"login": {
"value": "true"
},
"lang": {
"value": "ru-RU"
}
}
},
".test.google.com": {
"/": {
"id": {
"value": 155643
}
}
}
});

let cookies = await this.cookies.getAll();

this.logger.put("Cookies: " + JSON.stringify(cookies));

results.SKIP = 1;
return results;
}
Ví dụ về kết quả thiết lập mảng cookies

.set(host, path, name, value)

await this.cookies.set(host, path, name, value) - thiết lập một cookie đơn lẻ.

Phạm vi áp dụng của cookie phụ thuộc trực tiếp vào định dạng của domain được chỉ định, vì vậy trong host cần xét đến việc có dấu chấm trước host hay không:

  • nếu có chỉ định dấu chấm (this.cookies.set('.domain.com', ...)), cookie sẽ được dùng cho tất cả các subdomain (ví dụ a.domain.com, b.a.domain.com)
  • nếu host được chỉ định không có dấu chấm ở trước (this.cookies.set('site.com', ...)), thì cookie sẽ được sử dụng chỉ cho host đã chỉ định (host-only cookie) và không được gửi tới các subdomain
thông tin

Sự khác biệt này cực kỳ quan trọng, vì việc đồng thời tồn tại cookie có dấu chấm và không có thể dẫn đến trùng lặp và hoạt động không thể dự đoán của website. Để mô phỏng đúng, hãy luôn kiểm tra website mục tiêu thiết lập cookie như thế nào (có thuộc tính Domain hay không), và sử dụng định dạng tương ứng.

async parse(set, results) {
this.logger.put("Start scraping query: " + set.query);

await this.cookies.set('.a-parser.com', '/', 'Test-cookie-1', 1);
await this.cookies.set('.a-parser.com', '/', 'Test-cookie-2', 'test-value');

let cookies = await this.cookies.getAll();

this.logger.put("Cookies: " + JSON.stringify(cookies));

results.SKIP = 1;
return results;
}
Ví dụ về kết quả thiết lập cookie đơn lẻ

await this.proxy.*

Làm việc với proxy

.next()

Đổi sang proxy tiếp theo, proxy cũ sẽ không còn được dùng cho truy vấn hiện tại

.ban()

Đổi và ban proxy (cần dùng khi dịch vụ chặn hoạt động theo IP), proxy sẽ bị ban trong khoảng thời gian, được chỉ định trong cài đặt của công cụ cào dữ liệu (proxybannedcleanup)

.get()

Lấy proxy hiện tại (proxy cuối cùng đã được dùng để thực hiện truy vấn)

.set(proxy, noChange?)

await this.proxy.set('http://127.0.0.1:8080', true) - đặt proxy cho yêu cầu tiếp theo. Tham số noChange không bắt buộc, nếu đặt true thì proxy sẽ không thay đổi giữa các lần thử. Mặc định noChange = false

await this.sessionManager.*

Các phương thức để làm việc với phiên. Mỗi phiên luôn lưu proxy đã sử dụng và cookie. Ngoài ra có thể lưu dữ liệu tùy ý bổ sung. Để sử dụng phiên trong công cụ cào dữ liệu JS trước tiên phải khởi tạo Trình quản lý phiên. Việc này được thực hiện bằng phương thức await this.sessionManagerinit() trong init()

.init(opts?)

Khởi tạo Trình quản lý session. Làm đối số có thể truyền một đối tượng (opts) với các tham số bổ sung (tất cả tham số đều không bắt buộc):

  • name - cho phép ghi đè tên của công cụ cào dữ liệu mà các session thuộc về, mặc định bằng tên của công cụ cào dữ liệu đang thực hiện khởi tạo
  • waitForSession - chỉ ra cho công cụ cào dữ liệu chờ phiên cho đến khi nó xuất hiện (điều này chỉ có ý nghĩa khi có nhiều job chạy, ví dụ một job tạo phiên, job khác dùng chúng), tức là .get().reset() sẽ luôn chờ phiên
  • domain - chỉ ra tìm phiên trong tất cả các phiên đã lưu của công cụ cào dữ liệu này (nếu không chỉ định giá trị), hoặc chỉ cho một domain cụ thể (cần chỉ định domain có dấu chấm ở đầu, ví dụ .site.com)
  • sessionsKey - cho phép đặt thủ công tên kho lưu trữ phiên, nếu không được đặt thì tên sẽ được tạo tự động dựa trên name (hoặc tên công cụ cào dữ liệu nếu name không được đặt), domain và trình kiểm tra proxy
  • expire - đặt thời gian sống của session tính bằng phút, mặc định không giới hạn

Ví dụ sử dụng:

async init() {
await this.sessionManager.init({
name: 'JS::test',
expire: 15 * 60
});
}

.get(opts?)

Lấy session mới, cần gọi trước khi thực hiện truy vấn (trước lần thử đầu tiên). Trả về một đối tượng với dữ liệu tùy ý đã được lưu trong session. Làm đối số có thể truyền một đối tượng (opts) với các tham số bổ sung (tất cả tham số đều không bắt buộc):

  • waitTimeout - có thể chỉ định số phút chờ phiên xuất hiện, hoạt động độc lập với tham số waitForSession trong .init() (bỏ qua nó), khi hết thời gian sẽ dùng phiên rỗng
  • tag - lấy session với tag đã chỉ định, ví dụ có thể dùng tên domain để gán session với các domain từ đó chúng được nhận

Ví dụ sử dụng:

await this.sessionManager.get({
waitTimeout: 10,
tag: 'test session'
})

.reset(opts?)

Xóa cookie và lấy session mới. Cần dùng nếu truy vấn với session hiện tại không thành công. Trả về một đối tượng với dữ liệu tùy ý đã được lưu trong session. Làm đối số có thể truyền một đối tượng (opts) với các tham số bổ sung (tất cả tham số đều không bắt buộc):

  • waitTimeout - có thể chỉ định số phút chờ phiên xuất hiện, hoạt động độc lập với tham số waitForSession trong .init() (bỏ qua nó), khi hết thời gian sẽ dùng phiên rỗng
  • tag - lấy session với tag đã chỉ định, ví dụ có thể dùng tên domain để gán session với các domain từ đó chúng được nhận

Ví dụ sử dụng:

await this.sessionManager.reset({
waitTimeout: 5,
tag: 'test session'
})

.save(sessionOpts?, saveOpts?)

Lưu session thành công với khả năng lưu dữ liệu tùy ý trong session. Hỗ trợ 2 đối số không bắt buộc:

  • sessionOpts - dữ liệu tùy ý để lưu trong session, có thể là số, chuỗi, mảng hoặc đối tượng
  • saveOpts - đối tượng với các tham số lưu session:
    • multiply - tham số không bắt buộc, cho phép nhân bản session, giá trị cần chỉ định là một số
    • tag - tham số không bắt buộc, đặt tag cho session được lưu, ví dụ có thể dùng tên domain để gán session với các domain từ đó chúng được nhận

Ví dụ sử dụng:

await this.sessionManager.save('some data here', {
multiply: 3,
tag: 'test session'
})

.count()

Trả về số lượng session cho Trình quản lý session hiện tại

Ví dụ sử dụng:

let sesCount = await this.sessionManager.count();

.removeById(sessionId)

Xóa tất cả session với id đã chỉ định. Trả về số lượng session đã xóa. Id của session hiện tại nằm trong biến this.sessionId Ví dụ sử dụng:

const removedCount = await this.sessionManager.removeById(this.sessionId);

Ví dụ sử dụng Trình quản lý session tổng hợp

async init() {
await this.sessionManager.init({
expire: 15 * 60
});
}

async parse(set, results) {
let ses = await this.sessionManager.get();

for(let attempt = 1; attempt <= this.conf.proxyretries; attempt++) {
if(ses)
this.logger.put('Data from session:', ses);
const { success, data } = await this.request('GET', set.query, {}, { attempt });
if(success) {
// process data here
results.success = 1;
break;
} else if(attempt < this.conf.proxyretries) {
const removedCount = await this.sessionManager.removeById(this.sessionId);
this.logger.put(`Removed ${removedCount} bad sessions with id #${this.sessionId}`);
ses = await this.sessionManager.reset();
}
}

if(results.success) {
await this.sessionManager.save('Some data', { multiply: 2 });
this.logger.put(`Total we have ${await this.sessionManager.count()} sessions`);
}

return results;
}
Ví dụ về lưu dữ liệu tùy ý và lấy lại sau đó

Phương thức truy vấn await this.request

Phương thức GET

Có thể truyền tham số truy vấn trực tiếp trong chuỗi truy vấn https://a-parser.com/users/?type=staff:

const { success, data, headers } = await this.request('GET', 'https://a-parser.com/users/?type=staff');

Hoặc dưới dạng object trong queryParams, trong đó key: value tương đương param=value:

const { success, data, headers } = await this.request('GET', 'https://a-parser.com/users/', {
type: 'staff'
});

Phương thức POST

Nếu dùng phương thức POST, thân truy vấn có thể được truyền theo hai cách:

  • Liệt kê tên biến và giá trị của chúng trong queryParams, ví dụ:

    {
    "key": set.query,
    "id": 1234,
    "type": "text"
    }
  • Liệt kê chúng trong opts.body, ví dụ:

    body: 'key=' + set.query + '&id=1234&type=text'

Nếu thân yêu cầu được truyền dưới dạng object, nó sẽ tự động được chuyển thành dạng form-urlencoded, đồng thời nếu chỉ định body và không chỉ định header content-type thì sẽ tự động gán content-type: application/x-www-form-urlencoded:

const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {
title: 'foo,',
body: 'bar',
userId: 1
});

Nếu thân của truy vấn POST là chuỗi hoặc buffer, thì nó sẽ được truyền nguyên như vậy:

// truy vấn với chuỗi
const string = 'title=foo&body=bar&userId=1';
const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {}, {
body: string
});

// truy vấn với buffer
const string = 'title=foo&body=bar&userId=1';
const buf = Buffer.from(string, 'utf8');
const { success, data, headers } = await this.request('POST', 'https://jsonplaceholder.typicode.com/posts', {}, {
body: buf
});

Tải lên tệp

Gửi file bằng truy vấn POST bằng mô-đun form-data:

const file = fs.readFileSync('pathToFile');
const FormData = require('form-data');
const format = new FormData();
format.append('file', file, 'fileName.ext');

const { success, data, headers } = await this.request('POST', 'https://file.io', {}, {
headers: format.getHeaders(),
body: format.getBuffer()
});

Ví dụ gửi file trong truy vấn POST với kiểu nội dung multipart/form-data:

const EOL = '\r\n';
const file = fs.readFileSync('pathToFile');
const boundary = '----WebKitFormBoundary' + String(Math.random()).slice(2);
const requestHeaders = {
'content-type': 'multipart/form-data; boundary=' + boundary
};

const body = '--'
+ boundary
+ EOL
+ 'Content-Disposition: form-data; name="file"; filename="fileName.ext"'
+ EOL
+ 'Content-Type: text/html'
+ EOL
+ EOL
+ file
+ EOL
+ '--'
+ boundary
+ '--';

const { success, data, headers } = await this.request('POST', 'https://file.io', {}, {
headers: requestHeaders,
body
});