ایجاد یک خزنده ساده با Nodejs برای دانلود تلفظ صحیح کلمات از لانگمن

خزیدن در یک وب‌سایت و شروع به استخراج اطلاعات اون، یکی از کارهای ساده و جذابیه که می‌شه با استفاده از Node.js انجام داد. من توی یکی از پروژه‌های خودم، نیاز به دانلود تلفظ صحیح کلمات انگلیسی از سایت دیکشنری آنلاین لانگمن داشتم، تصمیم گرفتم که این ابزار کوچک رو تحت عنوان یک پروژه آموزشی در اختیار بقیه قرار بدم.

این پروژه از طریق مخزن گیت‌هاب در دسترس هست و اگر فکر می‌کنید این آموزش برای شما ساده هست، می‌تونید کد نهایی رو در اختیار داشته باشید.

کتابخانه‌های استفاده شده

برای توسعه این ابزار، نیاز به دو کتابخانه اساسی جهت خزیدن در وب‌سایت و دانلود محتوا خواهیم داشت که هریک به شرح زیر هستند:

کتابخانه Request

با استفاده از این کتابخانه، می‌تونیم درخواست‌های HTTP ارسال کنیم و جوابی که از سمت سرور ارسال می‌شه رو دریافت کنیم؛ پس حتماً متوجه شدید که این کتابخانه به ما کمک می‌کنه که سورس HTML سایت رو دریافت کنیم تا بتونیم محتوای داخل یک صفحه رو بخونیم.

کتابخانه Cheerio

این کتابخانه که APIهایی شبیه به jQuery داره به شما کمک می‌کنه تا به المنت‌های مختلف در صفحه با استفاده از Selectorهایی شبیه به آنچه که در jQuery داشتیم، دست پیدا کنیم.

دست به عمل بشیم…

در ابتدا و طبق عادت خودم، ترمینال رو باز می‌کنیم و دستور ایجاد یک پروژه npm رو می‌زنیم:

npm init -y

با استفاده از دستور فوق، یک فایل package.json در پوشه‌ای که هستید ایجاد می‌شه؛ باید دو کتابخانه‌ای که قبلاً اسم بردم رو نصب کنیم، پس در ادامه در محیط ترمینال می‌نویسم:

npm install cheerio request --save

کمی صبر می‌کنیم تا کتابخانه‌ها نصب بشه…

یک فایل با اسم index.js (یا هر اسمی که خودتون دوست دارید) بسازید و کدهای زیر رو کپی و پیست کنید:

const request = require('request');
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const downloadPath = path.resolve(__dirname, 'downloads/');
const ameClass = '.speaker.amefile',
  breClass = '.speaker.brefile'
  
function normalizer(word) {
  return word.trim().replace(/\s+/g, '-').toLowerCase();
}

function fileDownloader(link, fileName) {
  if (!fs.existsSync(downloadPath)) {
    fs.mkdir(downloadPath);
  }

  let req = request
    .get(link)
    .on('error', err => {
      return err;
    })
    .on('response', res => {
      if (res.statusCode == 200)
        req.pipe(fs.createWriteStream(path.resolve(downloadPath, `${fileName}.mp3`)));
    })
}

function main(link, word) {

  request(link, (error, response, body) => {
    if (error) return error;
    const $ = cheerio.load(body);
    const ameFile = $(ameClass).data('src-mp3'),
      breFile = $(breClass).data('src-mp3');
    
    // Downloading American Pronunciation
    fileDownloader(ameFile, `ame-${word}`);
    
    // Downloading British Pronunciation
    fileDownloader(breFile, `bre-${word}`);
    
  )}
}

let word = normalizer(process.argv[2]);
main(`http://www.ldoceonline.com/dictionary/${word}`, word);

خب؛ جای هیچ نگرانی‌ای برای گیج شدن نیست! قسمت به قسمت کدها رو توضیح می‌دیم تا ببینیم هر قسمت چه کاری رو انجام می‌ده!

خب، به این قسمت از کد توجه کنید که در حال تعریف چندین متغیر هستیم:

const request = require('request');
const fs = require('fs');
const path = require('path');
const cheerio = require('cheerio');

const downloadPath = path.resolve(__dirname, 'downloads/');
const ameClass = '.speaker.amefile',
  breClass = '.speaker.brefile'

متغیرهای request، fs، path و cheerio هرکدام برای فراخوانی کتابخانه‌های خودشون هستند. با استفاده از کتابخانه‌های path و fs که همراه با Node.js هست، مسیر ذخیره فایل و خود عملیات ذخیره‌سازی فایل‌های دانلود شده را -به‌ترتیب- انجام می‌دیم.

متغیرهای ameClass و breClass هم در واقع selectorهای المانی‌هایی هستند که قرار است فایل تلفظ لغت را از آن‌ها استخراج کنیم. در ادامه بیشتر توضیح خواهم داد.

اینکه چطور می‌خوایم دیتایی که لازم داریم را از سایت لانگمن استخراج کنیم، به این شکل هست: فرض کنیم می‌خوایم تلفظ لغت take off را دانلود کنیم. اگر این لغت داخل سایت جستجو بشه، می‌بینید که URL به این شکل هست:

http://www.ldoceonline.com/dictionary/take-off

چند نکته باید در رابطه با URL لغات توجه کنیم؛ اول اینکه لغت‌های ترکیبی با خط تیره جدا شده و دوم، تمامی حروف لغت‌ها بصورت کوچک (lowercase) نوشته شده‌اند. پس باید در ابتدا یک فانکشنی رو تعریف کنیم که کار استاندارسازی (یا اصطلاحاً normalize کردن) را برای ما انجام بده، پس به این صورت تعریف می‌کنیم:

function normalizer(word) {
  return word.trim().replace(/\s+/g, '-').toLowerCase();
}

حالا اگر URL بالا را باز کنید و کمی به پایین اسکرول کنید، می‌بینید که دو تا آیکون بلندگو (استقلالی و پرسپولیسی) هست که استقلالی تلفظ آمریکایی و پرسپولیسی تلفظ بریتانیایی هر لغت رو نشون می‌ده.

اگر از ابزار inspect element مرورگر استفاده کنید، می‌بینید که هر کدام از این‌ها یک تگ span به شکل زیر هستند:

<span
data-src-mp3=”http://www.ldoceonline.com/media/english/breProns/brelasdetake-off.mp3"
class=”speaker brefile fa fa-volume-up”
title=”…”
>
</span>

دو attribute مهم در این تگ وجود داره؛ یکی data-src-mp3 و دیگری class. با استفاده از اولی می‌تونیم URL فایل صوتی رو بدست بیاریم و با استفاده از دومی می‌شه حین خزیدن در داخل سایت بهش اشاره کنیم و اون رو به اصطلاح select کنیم. پس باید متوجه شده باشید که دو متغیر ameClass و breClass برای چی تعریف شدند!

قسمت بعد کار، نیاز داریم تا یک دانلودر برای URL فایل صوتی ما بنویسیم؛ در واقع، یک فانکشن که لینک و اسم فایل بعنوان ورودی بگیره، و فایل رو برای ما ذخیره کنه. پس قسمت بعدی از کد به این شکل تعریف شده:

function fileDownloader(link, fileName) {
  if (!fs.existsSync(downloadPath)) {
    fs.mkdir(downloadPath);
  }

  let req = request
    .get(link)
    .on('error', err => {
      return err;
    })
    .on('response', res => {
      if (res.statusCode == 200)
        req.pipe(fs.createWriteStream(path.resolve(downloadPath, `${fileName}.mp3`)));
    })
}

در ابتدای این کد، ابتدا چک می‌کنیم که اگر مسیری که قرار هست فایل اونجا ذخیره بشه وجود نداره (فولدرش موجود نباشه) اول فولدر رو ایجاد کنه. بعد از اون، با استفاده از ماژول request، درخواست به URL ارسال می‌کنیم و محتوا رو دانلود می‌کنیم.

آخرین فانکشن مورد نیاز ما، فانکشن main هست که کارهای اصلی قرار هست در اینجا رخ بده؛ به این شکل تعریف می‌کنیم:

function main(link, word) {

  request(link, (error, response, body) => {
    if (error) return error;

    const $ = cheerio.load(body);
    const ameFile = $(ameClass).data('src-mp3'),
      breFile = $(breClass).data('src-mp3');
      
    // Downloading American Pronunciation
    fileDownloader(ameFile, `ame-${word}`);

    // Downloading British Pronunciation
    fileDownloader(breFile, `bre-${word}`);
  })
}

در این فانکشن، ابتدا درخواست به سایت لانگمن ارسال می‌کنیم تا محتوای سایت (سورس HTML) رو دریافت کنیم؛ این کار رو با استفاده از ماژول request انجام می‌دیم. پس از آن، از ماژول cheerio استفاده می‌کنیم تا سورس HTML رو داخل متغیر $ لود کنیم و classهایی که قبل‌تر در متغیرهای ameClass و breClass ذخیره کردیم را با استفاده از cheerio پیدا و به محتوای data-src-mp3 دسترسی پیدا کنیم. در نهایت، با استفاده از فانکشن fileDownloader، فایل‌ها را دانلود می‌کنیم.

حالا که همه چیز آماده هست، با استفاده از محیط CLI، لغت کاربر را دریافت و اقدام به دانلود تلفظ صوتی می‌کنیم، پس کافیه از این به بعد دستور پایین رو بزنیم تا فایل‌ها دانلود بشه:

node index.js 'take off'

به جای کلمه take off می‌تونید کلمه مورد نظر خودتون رو قرار بدید.