Dandy Now!
  • [개인] 워크넷 맞춤 구직 정보 이메일로 받기 #2 | dotenv, nodemailer를 추가해 크롤링 정보를 개인 이메일로 발송
    2022년 04월 15일 16시 24분 53초에 업로드 된 글입니다.
    작성자: DandyNow
    728x90
    반응형

    | 개발 동기

    매번 워크넷에 들어가 비슷한 검색어를 입력하는 것이 번거로웠다. 스케줄링까지 적용해 매일 정해진 시간에 자동으로 이메일 구직 정보를 받아보는 프로그램을 만들고자 한다.

     

    | 개발 목표

    1. 워크넷 정보를 크롤링한다.
    2. 크롤링된 정보를 개인 메일로 발송한다.
    3. 크롤링된 정보를 매일 지정한 시간에 개인 메일로 발송한다.
    4. 이상의 기능에 GUI를 적용한다.

     

    | 사용 기술

    이번에 사용한 모듈은 dotenv, nodemailer이다.

    dotenv는 깃허브에 이 프로젝트를 커밋, 푸시하더라도 구글로부터 받은 메일 키는 제외되도록 하기 위해 사용하였다.

    nodemailer는 크롤링된 정보를 개인 이메일로 발송하기 위해 사용하였다. 

    ※ node.js에서 진행한 이번 프로젝트에서 현재까지 사용한 모듈은 axios, cheerio, dotenv, nodemailer이다.

     

    | 진행 현황

    개발 목표 중 1, 2를 달성하였다.

    #1에서 진행했던 크롤링 정보에 "경력, 학력, 위치"를 추가했다.

     

    | 다음 단계

    cron 식을 적용해 매일 정해진 시간에 크롤링하여 메일을 발송하도록 할 것이다.

     

    | 문제 해결

    1. cheerio의 find함수를 사용할 때 원하는 정보를 타깃 하기 위한 css 선택자를 지정하는 것이 어려웠다. eq()를 사용해 적절히 해결했다.
    2. 크롤링된 문자열에 원하지 않는 문자나 문자열을 replace함수를 사용해 제거하고자 하였다. 이때 정규식이 지식이 짧음을 깊이 실감했다! 원하지 않는 문자열이 "학력"에서 발생했는데 모든 경우에 동일한 문자열이라 정규식을 사용하지 않고 해결했다.

     

    | 작성 코드

    const axios = require("axios");
    const cheerio = require("cheerio");
    
    require("dotenv").config({ path: `nodemailer/.env` });
    const nodemailer = require("./nodemailer");
    
    // 크롤링 대상
    const getHTML = async (keyword, resultCnt, regionNumber) => {
      try {
        const html =
          // getJobs()을 호출할 때 워크넷 url은 "검색어(encodeURI), 검색 결과 수(resultCnt), 지역코드(regionNumber)"를 동적으로 받음
          (
            await axios.get(
              `https://www.work.go.kr/empInfo/empInfoSrch/list/dtlEmpSrchList.do?careerTo=&keywordJobCd=&occupation=&templateInfo=&shsyWorkSecd=&rot2WorkYn=&payGbn=&resultCnt=${resultCnt}&keywordJobCont=&cert=&cloDateStdt=&moreCon=&minPay=&codeDepth2Info=11000&isChkLocCall=&sortFieldInfo=DATE&major=&resrDutyExcYn=&eodwYn=&sortField=DATE&staArea=&sortOrderBy=DESC&keyword=${encodeURI(
                keyword
              )}&termSearchGbn=all&carrEssYns=&benefitSrchAndOr=O&disableEmpHopeGbn=&webIsOut=&actServExcYn=&maxPay=&keywordStaAreaNm=&emailApplyYn=&listCookieInfo=DTL&pageCode=&codeDepth1Info=11000&keywordEtcYn=&publDutyExcYn=&keywordJobCdSeqNo=&exJobsCd=&templateDepthNmInfo=&computerPreferential=&regDateStdt=&employGbn=&empTpGbcd=&region=${regionNumber}&infaYn=&resultCntInfo=${resultCnt}&siteClcd=all&cloDateEndt=&sortOrderByInfo=DESC&currntPageNo=1&indArea=&careerTypes=&searchOn=Y&tlmgYn=&subEmpHopeYn=&academicGbn=&templateDepthNoInfo=&foriegn=&mealOfferClcd=&station=&moerButtonYn=&holidayGbn=&enterPriseGbn=all&academicGbnoEdu=noEdu&cloTermSearchGbn=all&keywordWantedTitle=&stationNm=&benefitGbn=&keywordFlag=&essCertChk=&isEmptyHeader=&depth2SelCode=&_csrf=afefc5c3-fc99-440b-9f6d-563efe24d151&keywordBusiNm=&preferentialGbn=&rot3WorkYn=&pfMatterPreferential=&regDateEndt=&staAreaLineInfo1=11000&staAreaLineInfo2=1&pageIndex=1&termContractMmcnt=&careerFrom=&laborHrShortYn=#viewSPL`
            )
          ).data;
        return html;
      } catch (e) {
        console.log(e);
      }
    };
    
    // 크롤링, 파싱 처리
    const parsing = async (page) => {
      const $ = cheerio.load(page);
      const jobs = [];
      const $jobList = $("tbody tr");
      $jobList.each((idx, node) => {
        const jobTitle = $(node).find(".cp-info-in:eq(0)").text().trim(); // 채용공고명
        const url =
          "https://www.work.go.kr" + $(node).find(".cp-info-in > a").attr("href"); // 채용공고 상세 보기 url
        const company = $(node).find(".cp_name:eq(0)").text().trim(); // 회사명
        const experience = $(node).find("em:eq(0)").text().trim(); // 경력
        const education = $(node)
          .find("em:eq(1)")
          .text()
          .trim()
          .replace("\n\t\t\t\t\t\t\t\t\t\t", ""); // 학력
        const location = $(node).find("em:eq(2)").text().trim(); // 회사 위치
    
        if (jobTitle != "") {
          jobs.push({
            jobTitle,
            url,
            company,
            experience,
            education,
            location,
          });
        }
      });
    
      console.log(jobs.length); // 총 검색된 게시글 수를 콘솔창에서 확인
    
      return jobs;
    };
    
    // 검색어(keyword), 검색 결과 수(resultCnt, 최대 검색 건수 기본값 10), 지역코드(regionNumber)
    const getJobs = async (keyword, resultCnt = "10", regionNumber = "") => {
      const html = await getHTML(keyword, resultCnt, regionNumber);
      const jobs = await parsing(html);
      console.log(jobs);
    
      // 이메일 테이블 생성 및 발송
      const h = [];
      h.push('<table style="border:1px solid black;">');
      h.push("<thead>");
      h.push("<tr>");
      h.push("<th>구인제목</th>");
      h.push("<th>회사명</th>");
      h.push("<th>경력</th>");
      h.push("<th>학력</th>");
      h.push("<th>위치</th>");
      h.push("</tr>");
      h.push("</thead>");
      h.push("<tbody>");
      jobs.forEach((j) => {
        h.push(`<tr>`);
        h.push(`<td><a href="${j.url}">${j.jobTitle}</a></td>`);
        h.push(`<td>${j.company}</td>`);
        h.push(`<td>${j.experience}</td>`);
        h.push(`<td>${j.education}</td>`);
        h.push(`<td>${j.location}</td>`);
        h.push(`</tr>`);
      });
      h.push("</tbody>");
      h.push("</table>");
    
      const message = {
        from: "ubithus@gmail.com",
        to: "ubithus@gmail.com",
        subject: `${keyword} 구인 회사 정보`,
        html: h.join(""),
      };
      await nodemailer.send(message);
    };
    
    getJobs("javascript", 1000, 26000); // 검색어 , 최대 검색 결과 수, 지역(26000 - 부산전체)

     

    // nodemailer/index.js
    const nodemailer = require("nodemailer");
    
    const config = {
      service: "gmail",
      host: "smtp.gmail.com",
      port: 587,
      secure: false,
      auth: {
        // dotenv 사용
        user: process.env.GOOGLE_MAIL,
        pass: process.env.GOOGLE_PASSWORD,
      },
    };
    
    const send = async (data) => {
      const transporter = nodemailer.createTransport(config);
      transporter.sendMail(data, (err, info) => {
        if (err) {
          console.log(err);
        } else {
          return info.response;
        }
      });
    };
    
    module.exports = {
      send,
    };

     

    [그림 1] 크롤링 정보를 개인 gmail로 발송한 결과

    728x90
    반응형
    댓글