Một loạt các series tools, apps, scripts cũng như những bài viết mổ xẻ, hướng dẫn code 1001 thứ khác nhau cùng với khanhne
F12
cơ bản là xong. Kết quả như sau<div class="adv-line">
<div class="adv-line-cell-1">...</div> <!-- Đây là nơi ta dùng appendChild để thêm thông tin tasks -->
<div class="adv-line-cell-2">
<span title="Site ID">...</span> <!-- Và dùng prepend vào <span> này để thêm thông tin employer -->
...
</div>
</div>
.adv-line
là được. Còn lại dùng querySelector
với selector trong ảnh là ra thông tin thôi. Thế là xong!/*
* Lấy thông tin employer từ seosprint (/member, /employer-tasks)
*/
async function getEmployerInfo(employerId) {
// Lấy tổng số tasks, thông tin member của employer
const getMemberInfo = new Promise(async function (resolve, reject) {
const response = await fetch(`/member/${employerId}`)
const body = await response.text()
const doc = (new DOMParser()).parseFromString(body, 'text/html')
resolve({
name: doc.querySelector('.mem-title').textContent,
id: doc.querySelector('.nick').textContent,
total_tasks: parseInt(doc.querySelector('a.mem-box').textContent) || 0
})
})
// Lấy số tasks đang active
const getActiveTasksCount = new Promise(async function (resolve, reject) {
const response = await fetch(`/employer-tasks/${employerId}`)
const body = await response.text()
const doc = (new DOMParser()).parseFromString(body, 'text/html')
resolve({
active_tasks: doc.querySelectorAll('.adv-line').length
})
})
// Dùng Promise.all để chạy cả 2 task cùng lúc
return (await Promise.all([getActiveTasksCount, getMemberInfo])).reduce((a, b) => Object.assign(a, b), {})
}
/member/<id>
và /employer-tasks/<id>
. Tiếp đó, ta dùng DOMParser.parseFromString() để parse từ HTML text đó sang dạng DOM tree, rồi dùng querySelector
để chọn element và trích dữ liệu ra là xong!Promise.all()
rồi nhưng tốc độ vẫn khá chậm, cần đợi vài giây sau khi load trang để load đầy đủ thông tin employer, với người vội vàng như mình thì điều này siêu siêu khó chịu. Vì thế mình "phát minh" ra phương án caching sau để tối ưu việc crawl này./*
* Wrapper để cache dữ liệu employer và quản lí việc gọi hàm getEmployerInfo tối ưu thời gian nhất
*/
async function getEmployerCached(employerId) {
if (!data[employerId]) {
// Lấy dữ liệu về employer nếu không có sẵn trong data
data[employerId] = {
...await getEmployerInfo(employerId)
}
console.log(`Got ${employerId}`)
} else {
// Trả về dữ liệu đã lưu trước đó trong data
console.log(`Cached ${employerId}`)
getEmployerInfo(employerId).then(employerData => {
// Chạy task update employerData trong background
data[employerId] = {
...employerData
}
console.log(`Updated ${employerId}`)
})
}
return data[employerId]
}
(function() {
'use strict';
const init = {}
for (const adv of document.querySelectorAll('.adv-line')) {
// Lấy ID employer
const employerId = adv.querySelector('a.ref-block-av').href.match(/\d+/g)[0]
const employerClass = `employer-${employerId}-tasks`
// Thêm element hiển thị thông tin tasks của employer
const template = document.createElement('template')
template.innerHTML = `<span style="white-space: nowrap" translate="no" class="${employerClass}">
<b class="active-tasks" title="Active tasks" style="color: green; font-size: 1.25rem"></b>
<span>/</span>
<b class="total-tasks" title="Total tasks"></b>
</span>`
adv.querySelector('.adv-line-cell-1').append(template.content.firstChild)
// Thêm element hiển thị thông tin member của employer
template.innerHTML = `<span style="margin-right: 14px" translate="no" class="${employerClass}">
<span class="member-info"></span>
</span>`
adv.querySelector('.advmoder > div > span:first-child').prepend(template.content.firstChild)
if (!init[employerId]) {
// Chỉ chạy task getEmployerCached nếu employerId chưa khởi tạo (chỉ chạy 1 lần duy nhất)
getEmployerCached(employerId).then(employerData => {
// Update thông tin trong web bằng dữ liệu lấy được
for (const elem of document.querySelectorAll(`.${employerClass} .active-tasks`)) {
elem.innerHTML = employerData.active_tasks
}
for (const elem of document.querySelectorAll(`.${employerClass} .total-tasks`)) {
elem.innerHTML = employerData.total_tasks
}
for (const elem of document.querySelectorAll(`.${employerClass} .member-info`)) {
elem.innerHTML = `${employerData.name} ${employerData.id}`
}
})
init[employerId] = true
}
}
})();
const init = {}
. Biến init
này có vai trò lưu lại thông tin rằng employer với ID đó đã được khởi tạo (lên lịch task crawl employer data) hay chưa. Nhờ vậy với mỗi employer trong page, ta chỉ chạy code crawl của employer đó đúng 1 lần duy nhất. Nếu không nhiều task getEmployerCached sẽ được gọi cùng lúc, dẫn đến việc crawl thông tin employer này bị lặp lại và làm chậm đi việc crawl thông tin các employer khác.document.createElement()
nhiều, cũng như code dễ hiểu, tường minh hơn hẳn.(function() {...})();
. Câu lệnh này đảm bảo rằng phần code bên trong chỉ chạy khi browser đã load xong page hiện tại, tránh các lỗi không đáng có do page chưa load xong hoàn toàn, thiếu element, thiếu nội dung backend,...// ==UserScript==
// @namespace https://github.com/KhanhhNe
// @name SeosprintPlus
// @description Chiến Seosprint siêu đỉnh cùng KhanhhNe và MMO4Me
// @icon https://github.com/KhanhhNe/CodeBanana-Tutorials/raw/main/seosprintplus/logo.png
// @copyright 2021, KhanhhNe (https://github.com/KhanhhNe)
// @license CC-BY-SA-3.0; http://creativecommons.org/licenses/by-sa/3.0/
// @license MIT
// @version 1.0.0
// @include https://seosprint.net/earn-task/*
// @grant none
// ==/UserScript==
// ==OpenUserJS==
// @author KhanhhNe
// ==/OpenUserJS==
/*jshint esversion: 9 */
/*jshint asi: true */
let data;
// Lấy dữ liệu từ localStorage
try {
data = JSON.parse(localStorage.getItem('employersData')) || {}
} catch (e) {
data = {}
}
// Update liên tục dữ liệu data lên localStorage
const int = setInterval(() => localStorage.setItem('employersData', JSON.stringify(data)), 1000)
// Ngưng update dữ liệu sau 20s vì không còn dữ liệu mới nữa (đã crawl đầy đủ)
setTimeout(() => clearInterval(int), 20000)
...
Fetch API
- gửi request bằng 'vanilla' JavaScript: https://www.javascripttutorial.net/javascript-fetch-api/DOMParser.parseFromString()
- parse HTML text sang DOM tree: https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromStringPromise.all()
- thực hiện đồng thời nhiều Promise cùng lúc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all<template>
element - công cụ hữu ích để làm placeholder tạo element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/templatetranslate
attribute - đánh dấu include/exclude HTML khỏi bộ dịch tự động của browser: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translateBạn cài tiện ích mở rộng TamperMonkey trên trình duyệt (có sẵn trên store của chrome và firefox) rồi vào link download này bấm install là nó cài xong. Vì chạy trên browser nên khó chuyển thành exe được bạnFencs có thể đóng gói lại file .exe cho tiện những ng ngu học như mình chỉ cần tải về click 1 phát là chạy từ a-z được không?
Mình nghĩ tính năng đó rất hữu ít
Thế là xong! Bạn có thể tìm và cài đặt bản code full qua link sau: https://openuserjs.org/scripts/KhanhhNe/SeosprintPlus
Bạn tải TamperMonkey trong store của trình duyệt về, vào link sau bấm nút install là cài xong rồi bạn. Giao diện seosprint trang earn-task của bạn sẽ tự update sau khi reload.có video hướng dãn chạy ko bác
Thế là xong! Bạn có thể tìm và cài đặt bản code full qua link sau: https://openuserjs.org/scripts/KhanhhNe/SeosprintPlus
Bạn tải TamperMonkey trong store của trình duyệt về, vào link sau bấm nút install là cài xong rồi bạn. Giao diện seosprint trang earn-task của bạn sẽ tự update sau khi reloadtool
tool này có tự động làm nv ko bBạn tải TamperMonkey trong store của trình duyệt về, vào link sau bấm nút install là cài xong rồi bạn. Giao diện seosprint trang earn-task của bạn sẽ tự update sau khi reload.
View attachment 181297
View attachment 181219
Minigame ở cuối bài nha, phần thưởng siêu hấp dẫn!
Chuyện là như này
Khá lâu về trước mình cũng như nhiều anh em newbie khác, tìm kiếm con đường kiếm tiền không vốn trên mạng. Sau một hồi lăn lộn nhiều site khác nhau, mình chạm mặt Seosprint. Cày cuốc đã đời thì mình thấy mức kiếm cũng k nhiều lắm, nản nên mình bỏ. Dạo gần đây thấy forum nổi lên vài bài về Seosprint và mọi người rủ nhau lập hội nhóm cày Seosprint, chia sẻ tasks,... làm mình rạo rực theo nên mình quyết định làm con tool này để giúp sức cho anh em MMO4Me chiến Seosprint ngon lành hơn.
Hôm nay mạn phép release bản đầu tiên của tool với tính năng đầu tiên:
Yêu employer ngay từ cái nhìn đầu tiên
Giao diện tasks (https://seosprint.net/earn-task/) sẽ được thêm phần hiển thị thông tin về tasks của employer cũng như thông tin cơ bản của employer. Demo như sau
View attachment 181210
Link tải (và cài đặt) https://openuserjs.org/scripts/KhanhhNe/SeosprintPlus
Chúc bạn kiếm tiền vui vẻ! Bái baiiii
.
.
.
Vậy là chỉ còn anh em coder mình thôi hehehehe. Chiến thôi!
HTML của Seosprint trông như nào
Vài bướcF12
cơ bản là xong. Kết quả như sau
1. Lấy cấu trúc HTML để hiển thị thông tin
View attachment 181211
View attachment 181213
Cấu trúc HTML cơ bản nó sẽ là thế này
HTML:<div class="adv-line"> <div class="adv-line-cell-1">...</div> <!-- Đây là nơi ta dùng appendChild để thêm thông tin tasks --> <div class="adv-line-cell-2"> <span title="Site ID">...</span> <!-- Và dùng prepend vào <span> này để thêm thông tin employer --> ... </div> </div>
2. Tìm cách crawl thông tin employer và tasks
View attachment 181216View attachment 181217
Về số tasks active, chỉ cần đếm số lượng.adv-line
là được. Còn lại dùngquerySelector
với selector trong ảnh là ra thông tin thôi. Thế là xong!
Inspect xong đến code chứ gì nữa
Với thông tin đã inspect được ở trên, ta sẽ lần lượt code từng phần, sau đó ghép nối chúng lại với nhau, up lên OpenUserJS cho mọi người dùng, viết bài đăng MMO4Me câu like là xong!
1. Crawl thông tin employer
JavaScript:/* * Lấy thông tin employer từ seosprint (/member, /employer-tasks) */ async function getEmployerInfo(employerId) { // Lấy tổng số tasks, thông tin member của employer const getMemberInfo = new Promise(async function (resolve, reject) { const response = await fetch(`/member/${employerId}`) const body = await response.text() const doc = (new DOMParser()).parseFromString(body, 'text/html') resolve({ name: doc.querySelector('.mem-title').textContent, id: doc.querySelector('.nick').textContent, total_tasks: parseInt(doc.querySelector('a.mem-box').textContent) || 0 }) }) // Lấy số tasks đang active const getActiveTasksCount = new Promise(async function (resolve, reject) { const response = await fetch(`/employer-tasks/${employerId}`) const body = await response.text() const doc = (new DOMParser()).parseFromString(body, 'text/html') resolve({ active_tasks: doc.querySelectorAll('.adv-line').length }) }) // Dùng Promise.all để chạy cả 2 task cùng lúc return (await Promise.all([getActiveTasksCount, getMemberInfo])).reduce((a, b) => Object.assign(a, b), {}) }
Để lấy thông tin employer, ta sử dụng Fetch API để request HTML từ link/member/<id>
và/employer-tasks/<id>
. Tiếp đó, ta dùng DOMParser.parseFromString() để parse từ HTML text đó sang dạng DOM tree, rồi dùngquerySelector
để chọn element và trích dữ liệu ra là xong!
Trong đoạn trên, mình viết theo dạng Promise và dùng Promise.all() để chạy cùng lúc 2 Promise đó. Mục đích của việc này là để tránh phải chạy lần lượt từng Promise đợi nhau, làm tăng (gấp đôi) tốc độ crawl dữ liệu.
2. Tối ưu việc crawl dữ liệu
Dù đã sử dụngPromise.all()
rồi nhưng tốc độ vẫn khá chậm, cần đợi vài giây sau khi load trang để load đầy đủ thông tin employer, với người vội vàng như mình thì điều này siêu siêu khó chịu. Vì thế mình "phát minh" ra phương án caching sau để tối ưu việc crawl này.
JavaScript:/* * Wrapper để cache dữ liệu employer và quản lí việc gọi hàm getEmployerInfo tối ưu thời gian nhất */ async function getEmployerCached(employerId) { if (!data[employerId]) { // Lấy dữ liệu về employer nếu không có sẵn trong data data[employerId] = { ...await getEmployerInfo(employerId) } console.log(`Got ${employerId}`) } else { // Trả về dữ liệu đã lưu trước đó trong data console.log(`Cached ${employerId}`) getEmployerInfo(employerId).then(employerData => { // Chạy task update employerData trong background data[employerId] = { ...employerData } console.log(`Updated ${employerId}`) }) } return data[employerId] }
Đoạn code này hoạt động như sau:
Sử dụng cơ chế đó, user sẽ chỉ cần đợi thông tin mỗi employer 1 lần duy nhất và không bao giờ phải đợi nữa, trong khi vẫn đảm bảo thông tin employer luôn được cập nhật thường xuyên. Chỉ thế thôi
- Nếu thông tin employer chưa crawl lần nào thì crawl, lưu lại rồi trả về
- Nếu thông tin employer đã có từ trướcthì
- Lên lịch crawl lại thông tin employer để update thông tin cũ
- Trả về thông tin đã crawl từ trước để đảm bảo user không phải đợi lâu
3. Hiển thị ra màn hình
Code "backend" thế là xong! Giờ ta cần hiển thị nó ra cho gọn gàng đẹp mắt để tiện dùng.
JavaScript:(function() { 'use strict'; const init = {} for (const adv of document.querySelectorAll('.adv-line')) { // Lấy ID employer const employerId = adv.querySelector('a.ref-block-av').href.match(/\d+/g)[0] const employerClass = `employer-${employerId}-tasks` // Thêm element hiển thị thông tin tasks của employer const template = document.createElement('template') template.innerHTML = `<span style="white-space: nowrap" translate="no" class="${employerClass}"> <b class="active-tasks" title="Active tasks" style="color: green; font-size: 1.25rem"></b> <span>/</span> <b class="total-tasks" title="Total tasks"></b> </span>` adv.querySelector('.adv-line-cell-1').append(template.content.firstChild) // Thêm element hiển thị thông tin member của employer template.innerHTML = `<span style="margin-right: 14px" translate="no" class="${employerClass}"> <span class="member-info"></span> </span>` adv.querySelector('.advmoder > div > span:first-child').prepend(template.content.firstChild) if (!init[employerId]) { // Chỉ chạy task getEmployerCached nếu employerId chưa khởi tạo (chỉ chạy 1 lần duy nhất) getEmployerCached(employerId).then(employerData => { // Update thông tin trong web bằng dữ liệu lấy được for (const elem of document.querySelectorAll(`.${employerClass} .active-tasks`)) { elem.innerHTML = employerData.active_tasks } for (const elem of document.querySelectorAll(`.${employerClass} .total-tasks`)) { elem.innerHTML = employerData.total_tasks } for (const elem of document.querySelectorAll(`.${employerClass} .member-info`)) { elem.innerHTML = `${employerData.name} ${employerData.id}` } }) init[employerId] = true } } })();
Đoạn này hoá ra lại dài dòng và nhiều trò mới nhất Chúng ta cùng nhau điểm qua từng thứ một nhé.
Đầu tiên để ý đoạnconst init = {}
. Biếninit
này có vai trò lưu lại thông tin rằng employer với ID đó đã được khởi tạo (lên lịch task crawl employer data) hay chưa. Nhờ vậy với mỗi employer trong page, ta chỉ chạy code crawl của employer đó đúng 1 lần duy nhất. Nếu không nhiều task getEmployerCached sẽ được gọi cùng lúc, dẫn đến việc crawl thông tin employer này bị lặp lại và làm chậm đi việc crawl thông tin các employer khác.
Tiếp theo, để ý việc sử dụng <template> nhé. Sở dĩ dùng cách này vì lí do: ngắn gọn. Cách này ngắn gọn và nhìn đẹp hơn cách sử dụngdocument.createElement()
nhiều, cũng như code dễ hiểu, tường minh hơn hẳn.
Cuối cùng là attribute translate="no". Attribute này giúp cho browser nhận ra thông điệp ý nghĩa: "Đừng biến số '1' của t thành 'one', làm zdậy chi?!?"
Và một lưu ý nho nhỏ nữa là việc gói gọn chúng vào(function() {...})();
. Câu lệnh này đảm bảo rằng phần code bên trong chỉ chạy khi browser đã load xong page hiện tại, tránh các lỗi không đáng có do page chưa load xong hoàn toàn, thiếu element, thiếu nội dung backend,...
4. Đem đống code này ném lên OpenUserJS (hoặc cài vào TamperMonkey)
Để làm việc này, thay vì sử dụng template gốc của TamperMonkey, ta sẽ sử dụng cấu trúc của OpenUserJS. Đoạn code hoàn chỉnh sẽ trông như sau
JavaScript:// ==UserScript== // @namespace https://github.com/KhanhhNe // @name SeosprintPlus // @description Chiến Seosprint siêu đỉnh cùng KhanhhNe và MMO4Me // @icon https://github.com/KhanhhNe/CodeBanana-Tutorials/raw/main/seosprintplus/logo.png // @copyright 2021, KhanhhNe (https://github.com/KhanhhNe) // @license CC-BY-SA-3.0; http://creativecommons.org/licenses/by-sa/3.0/ // @license MIT // @version 1.0.0 // @include https://seosprint.net/earn-task/* // @grant none // ==/UserScript== // ==OpenUserJS== // @author KhanhhNe // ==/OpenUserJS== /*jshint esversion: 9 */ /*jshint asi: true */ let data; // Lấy dữ liệu từ localStorage try { data = JSON.parse(localStorage.getItem('employersData')) || {} } catch (e) { data = {} } // Update liên tục dữ liệu data lên localStorage const int = setInterval(() => localStorage.setItem('employersData', JSON.stringify(data)), 1000) // Ngưng update dữ liệu sau 20s vì không còn dữ liệu mới nữa (đã crawl đầy đủ) setTimeout(() => clearInterval(int), 20000) ...
Thế là xong! Bạn có thể tìm và cài đặt bản code full qua link sau: https://openuserjs.org/scripts/KhanhhNe/SeosprintPlus
Minigame
Phần hướng dẫn hôm nay thế là xong. SeosprintPlus là một dự án tool mình sẽ duy trì lâu dài. Tuy nhiên mình lại không phải "chiên da" trong lĩnh vực cày bừa này, vì vậy mạn phép dùng ít quà mọn nhờ mọi người giúp đỡ Chi tiết như sau
Mọi người hãy comment một tính năng/đề xuất mới, comment đạt được lược react nhiều nhất hoặc hay nhất sẽ được hiện thực hoá trong phiên bản tiếp theo. Chủ nhân của comment may mắn cũng nhận được phần quà trị giá lên đến $1,000 cụ thể là 0.5MR, hy vọng sẽ là một món quà cho người comment cũng như động lực nho nhỏ cho các bạn để nghĩ ra những ý tưởng thật hay ho vì không chỉ nhận được quà, bạn còn được bộ tool miễn phí, mất gì đâu nào!
Lời kết
Bài viết hướng dẫn cho hôm nay chỉ đến đây thôi. Hy vọng mọi người biết thêm một chút kiến thức hữu ích cho bản thân. Nếu các bạn thấy hay (hoặc thấy thích việc đọc những bài như thế này dù không hiểu lắm/chả bao giờ làm theo) thì hãy nhấn Like (thả tim cũng được) và comment bên dưới để mình biết còn có người thích những gì mình viết ra nhé Và đừng quên tham gia minigame để nhận phần quà siêu hấp dẫn, siêu to khổng lồ nha.
Chúc mọi người một ngày vui vẻ và kiếm được thật nhiều tiền!!!
Một vài chiếc link hữu ích
Fetch API
- gửi request bằng 'vanilla' JavaScript: https://www.javascripttutorial.net/javascript-fetch-api/DOMParser.parseFromString()
- parse HTML text sang DOM tree: https://developer.mozilla.org/en-US/docs/Web/API/DOMParser/parseFromStringPromise.all()
- thực hiện đồng thời nhiều Promise cùng lúc: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise/all<template>
element - công cụ hữu ích để làm placeholder tạo element: https://developer.mozilla.org/en-US/docs/Web/HTML/Element/templatetranslate
attribute - đánh dấu include/exclude HTML khỏi bộ dịch tự động của browser: https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/translate- Tổng hợp code TamperMonkey của tui: https://openuserjs.org/users/KhanhhNe/scripts
- Full code trong bài viết: https://github.com/KhanhhNe/CodeBanana-Tutorials/tree/main/seosprintplus
- Ủng hộ tác giả: like/react (số lẻ lần), comment "Bài viết hay, có tâm, chất lượng, cảm ơn bạn rất rất rất nhiều"
View attachment 181218
cài hết rồi mà không biết nó chạy lsaoView attachment 181298sao mình cài rồi mà mở nó không chạy nhỉ? Mình thao tác saiởđoạn nào hả b?
bác quay cho em xem chứ e cài tool nó không hoạt động gì hếtUpdate 16/8/2021
Mình đã test lại và phát hiện ra lỗi tool không bắt được toàn bộ URL liên quan đến list tasks. Mình đã update lại trên Github và OpenUserJS, mọi người có thể update bằng link cũ nhé.
View attachment 181303
bấm gì để nó chạy nữa thế anh em
View attachment 181304
Làm sao nữa để nó hoạt động vậy bUpdate lần 2 16/8/2021
Mình thấy nhiều bạn thắc mắc cách sử dụng nên mình làm 1 video ngắn hướng dẫn cài đặt và sử dụng tool. Chúc mọi người kiếm tiền vui vẻ
làm vid như không :VUpdate lần 2 16/8/2021
Mình thấy nhiều bạn thắc mắc cách sử dụng nên mình làm 1 video ngắn hướng dẫn cài đặt và sử dụng tool. Chúc mọi người kiếm tiền vui vẻ
Làm sao nữa để nó hoạt động vậy b
Sau khi cài đặt xong vào lại seosprint là bạn sẽ thấy sự thay đổi trong giao diện, không cần làm thêm bước gì nữa cả, chỉ cần reload lại trang thôi. Trường hợp bạn vào vẫn không thấy khác biệt gì thì gửi screenshot cho mình bao gồm URL, tab Console của DevTools (F12 -> Console), hình ảnh hiển thị khi bạn click vào nút TamperMonkey trên trình duyệt, mình sẽ check cho bạn xem tại sao.làm vid như không :V
chưa thấy đoạn tool nó chạy luôn
Mình làm y chang video nhưng không có thay đổi gì hết bạnSau khi cài đặt xong vào lại seosprint là bạn sẽ thấy sự thay đổi trong giao diện, không cần làm thêm bước gì nữa cả, chỉ cần reload lại trang thôi. Trường hợp bạn vào vẫn không thấy khác biệt gì thì gửi screenshot cho mình bao gồm URL, tab Console của DevTools (F12 -> Console), hình ảnh hiển thị khi bạn click vào nút TamperMonkey trên trình duyệt, mình sẽ check cho bạn xem tại sao.