본문 바로가기

etc/개인프로젝트

사람인 기업 필터링 기능 만들기 (with Tampermonkey)

Tampermonkey


Tampermonkey웹 브라우저 확장 프로그램(chrome extention) 중 하나로, 주로 사용자가 웹 페이지의 동작을 수정하거나 개선하기 위해 스크립트를 실행할 수 있게 해주는 도구이다. Tampermonkey는 대부분의 주요 웹 브라우저에서 사용할 수 있으며, 사용자 지정 스크립트를 실행하여 웹 페이지에 자신만의 기능을 추가하거나 웹 사이트의 동작을 수정할 수 있다.

Tampermonkey를 사용하면 할 수 있는 것들:

1. 사용자 지정 스크립트 실행: Tampermonkey를 통해 웹 페이지에 JavaScript 스크립트를 삽입하고 실행가능. 이를 통해 웹 페이지의 내용을 수정하거나 기능을 확장할 수 있다.

2. 광고 차단: Tampermonkey를 사용하여 광고 차단 스크립트를 실행하면 광고를 숨기거나 제거가능.

3. 웹 페이지 개선: Tampermonkey를 사용하여 웹 페이지의 사용자 경험을 개선하거나 특정 기능을 추가할 수 있음.
ex. 웹 페이지의 테마를 변경

4. 사용자 지정 스크립트 저장 및 관리: Tampermonkey는 사용자가 작성한 스크립트를 저장하고 관리하는 기능을 제공.

Tampermonkey를 사용하려면 먼저 해당 브라우저 확장 프로그램 스토어에서 Tampermonkey 확장을 설치해야함.
그런 다음 원하는 스크립트를 작성하거나 Tampermonkey 스크립트 저장소에서 다른 사용자가 작성한 스크립트를 가져와 사용이 가능. 


 

사람인 기업 필터링 기능 추가해보기

구직 플랫폼 중 하나인 사람인에서 애드블록 처럼 안 보고 싶은 기업의 공고 (악성 기업이나 인력장사 보도방 공고) 는 계속 안보이게 하고싶어서 이 Tampermonkey를 이용하여 필터링 하는 기능을 스크립트를 작성하여 만들어 볼 것이다. 

우선 Tampermonkey 를 설치하고

https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ko 

 

Tampermonkey

Change the web at will with userscripts

chrome.google.com

밑의 openuserJS 링크에 들어가서 유저스크립트를 설치만 하면 된다. 

( 계속 업데이트 중)
https://openuserjs.org/scripts/katastrophe/jobBlocking_saramin_ver_0.1

 

About | jobBlocking_saramin_ver_0.1 | Userscripts | OpenUserJS

Are you sure you want to flag this script for potential inspection by a Moderator?

openuserjs.org

우측 상단의 install 클릭후 설치를 누르면 끝.

 

구현 과정 

( 글 접어 놨습니다. )

더보기

새 스크립트 만들기 클릭

 

// ==UserScript==
// @name         New Userscript
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       You
// @match        http://*/*
// @icon         data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    // Your code here...
})();

저기 function 안에 작동 시킬 원하는 스크립트를 적으면 된다.

중요한건 위의 Userscript 의 메타블록 작성인데 필수 메타태그는 공식문서를 참조하면 된다.

https://www.tampermonkey.net/documentation.php?locale=en 

 

Documentation | Tampermonkey

Documentation

www.tampermonkey.net

 

사람인 기업 필터링 스크립트 완성본

// ==UserScript==
// @name         jobBlocking_saramin_ver_0.1
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  try to take over the world!
// @author       katastrophe - nicebyy@naver.com
// @match        https://www.saramin.co.kr/zf_user/jobs/list/*
// @icon         https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @license MIT
// @copyright 2023, katastrophe (https://openuserjs.org/users/katastrophe)
// ==/UserScript==

(function () {

    let buttonMap = new Map();

    const createData = (key, element) => {

        const companyName = element.querySelector('.company_nm > a').innerText.replaceAll(' ', '');
        const jobDescription = element.querySelector('.notification_info > .job_tit > a > span').innerText.replaceAll(' ', '');

        const jobInfo = {
            key: key,
            companyName: companyName,
            jobDescription: jobDescription
        };
        return jobInfo;
    };

    const createButton = (key, element) => {

        let button = document.createElement('button');
        // html
        button.type = 'button';
        button.innerHTML = key;
        button.className = 'btn_del';

        //css
        button.style.marginTop = '2px';
        button.style.marginRight = '8px';

        button.style.display = 'inline-block';
        button.style.marginLeft = '4px';
        button.style.width = '13px';
        button.style.height = '17px';
        button.style.verticalAlign = 'top';
        button.style.fontSize = '0';
        button.style.background = 'url(//www.saraminimage.co.kr/sri/person/search_panel/spr_search_panel.png) no-repeat 2px -186px';

        const divBox = element.querySelector('.company_nm');
        divBox.style.width = '200px';
        const pos = element.querySelector('.company_nm > .interested_corp');

        divBox.insertBefore(button, pos);
        button.addEventListener("click", () => {

            let result = GM_getValue(key, null);

            if (result == null) {
                element.style.display = 'none';
                GM_setValue(key, createData(key, element));
                buttonMap.set(key, false);
                displayAllJobs(false);
            }
        });
    };

    const displayAllJobs = (isInit) => { // Find all job elements

        if(isInit){
            buttonMap = new Map();
        }
        const elements = document.querySelectorAll('[id^="rec-"]');

        elements.forEach(element => {
            const recId = element.getAttribute('id'); // jobKey
            const recValue = GM_getValue(recId, null);

            if (recValue !== null) { 
                element.style.display = 'none';

            } else if (isInit) {
                buttonMap.set(recId, true);
                createButton(recId, element);
            }

        });
    }

    const removeBlockingList = ()=>{
        for(let key of GM_listValues()){
            GM_deleteValue(key);
        }
    };

    const createRemoveButton = ()=>{

        let button = document.createElement('button');
        // html
        button.type = 'button';
        button.className = 'sri_btn_md';

        //css
        button.style.display = 'inline-flex';
        button.style.marginLeft = '-34%';

        let buttonText = document.createElement('span');
        buttonText.innerHTML = 'blocking 초기화';
        buttonText.className = 'sri_btn_immediately';

        button.appendChild(buttonText);

        const divBox = document.querySelector('.list_info');
        const pos = document.querySelector('.list_select');

        divBox.insertBefore(button, pos);
        button.addEventListener("click", () => {

            if (!confirm("Blocking List 를 초기화 합니다..")) {
                alert("취소를 누르셨습니다.");
            } else {
                alert("초기화 완료.");
                removeBlockingList();
                location.reload();
            }
        });
    }

    document.querySelector('#page_count').addEventListener("change", () => {
        setTimeout(function(){
            displayAllJobs(true);
        },1000);
    });
    document.querySelector('#sort').addEventListener("change", () => {
        setTimeout(function(){
            displayAllJobs(true);
        },1000);
    });
    if(document.querySelector('#show_applied_recruit')!=null){
        document.querySelector('#show_applied_recruit').addEventListener("change", () => {
            setTimeout(function(){
                displayAllJobs(true);
            },1000);
        });
    }
    document.querySelector('#filter_ai_head_hunting').addEventListener("change", () => {
        setTimeout(function(){
            displayAllJobs(true);
        },1000);
    });
    document.querySelector('#filter_quick_apply').addEventListener("change", () => {
        setTimeout(function(){
            displayAllJobs(true);
        },1000);
    });
        'use strict';
        createRemoveButton();
        displayAllJobs(true);
    
})();

 

 

지우기 전

지역별로 검색을 했을 때 못 보던 X 표시와 blocking 초기화 버튼이 보일 것이다.

맨 상단 공고 하나를 지워보면

 

지운 후

다음과 같이 없어진다. 만약 다시 보이게 하고 싶으면 blocking 초기화 버튼을 누르면 된다.

 

데이터 관련 스토리지는 GM 관련 내부 Strorage API 를 사용하였다. 

데이터를 확인하고 싶다면 설정에서 모드를 상급자로 변경 뒤 

다시 에디터쪽으로 가보면 Storage 탭이 새로 생긴 것을 볼 수 있다.

저장포맷은 Key-value 방식의 Json 을 사용하는 듯 하다.

 

구현하면서 아쉬운점은 상단에 페이징 옵션이나 필터링 관련 기능쪽인데

페이징 방식을 변경할 때 버튼이 생성되고나서 새로운 페이징결과가 적용되기 때문에 이는 직접제어 할 수 없어서 setTimeout 을 이용하여 1초뒤에 버튼이 보이게 만들었다. 네트워크 환경이 불안정하거나 페이지를 불러오는데 1초이상 걸린다면 적용이 안될 수도 있다.

그럴땐 소스 내부의 setTimeout 시간을 1000( 1000ms = 1초 )  보다 높은 값으로 설정하면 동작한다.

 

나중에 내가 차단했던 기업을 골라서 다시 취소할 수 있는 기능을 시간나면 만들어 볼 예정이다.

Recent Posts
Popular Posts
Archives
Visits
Today
Yesterday