Skip to content

Instantly share code, notes, and snippets.

@hmmhmmhm
Created May 16, 2019 16:49
Show Gist options
  • Save hmmhmmhm/3f8768245c50b9d443590e675352f09a to your computer and use it in GitHub Desktop.
Save hmmhmmhm/3f8768245c50b9d443590e675352f09a to your computer and use it in GitHub Desktop.
const puppeteer = require('puppeteer');
const logger = require('./logger');
const Pool = require('./pool');
const LRU = require("lru-cache");
var browserErrorCount = 0;
var collectInterval = null;
var hitInterval = null;
async function pageAccelerate(page){
await page.setRequestInterception(true);
page.on('request', request => {
if (request.resourceType() === 'image')
request.abort()
else
request.continue()
})
};
let chucheon = (async () => {
// 추천 예정인 게시글 목록
let preChucheonList = {};
// 추천이 진행 중인 게시글 목록
let processingChucheonList = {};
// 이미 추천한 게시글 목록 캐시
let options = {
max: 500000,
maxAge: 1000 * 60 * 60 }
let votedCache = new LRU(options);
let ignoredCache = new LRU(options);
const browser = await puppeteer.launch({headless: true})
const refreshPage = await browser.newPage()
// 로그인 합니다.
let initTry = true
try{
await refreshPage.goto('https://user.ruliweb.com/member/login', {waitUntil: 'domcontentloaded'})
// Hit ID & PW
await refreshPage.type('#user_id', '', {delay: 30})
await refreshPage.type('#user_pw', '', {delay: 30})
// 모든 메시지는 뜨는 즉시 accept 합니다.
refreshPage.on('dialog', async dialog => {
console.log(dialog.message())
await dialog.accept()
})
await refreshPage.click('#login_submit', {delay: 200})
await refreshPage.waitForNavigation()
}catch(e){
logger('최초 로딩에 실패하였습니다. 다시 재시작을 시도합니다..')
initTry = false
}
let isInitError = false
if(!initTry){
logger(`시스템이 자동으로 재시작 됩니다.`)
if(collectInterval !== null)
clearInterval(collectInterval)
if(hitInterval !== null)
clearInterval(hitInterval)
await browser.close()
isInitError = true
console.error('reboot')
process.exit(1)
}
if(isInitError) return
logger('스마트 커뮤니티 마케팅 시스템 작동하였습니다.')
// 페이지 재사용 풀을 생성합니다.
//const pool = new Pool(browser)
// 페이지 내 최신 게시글 목록을 수집합니다.
async function collectBoardFresh(page){
// collectedData
// [0] collectedDocs
// [1] failureDocs
let collected = await page.evaluate(() => {
function isBad(text){
for(var bad of bads){
var badCount = 0
var badChars = bad.split('')
if(typeof ignores[bad] != 'undefined'){
for(var ignoreWord of ignores[bad]){
if(text.indexOf(ignoreWord) != -1)
return false
}
}
for(var badChar of badChars){
if(text.indexOf(badChar) != -1)
badCount++
}
if(badCount == badChars.length)
return true
}
return false
}
//document.querySelector('button[type=submit]').click()
var docItems = document.querySelectorAll('.board_list_table')[0].querySelectorAll('.deco')
var collectedDocs = []
for(var docItem of docItems){
var docDetail = docItem.parentElement.parentElement
if(docDetail.className.indexOf('table_body notice') == -1){
if(!isBad(docItem.innerText)){
collectedDocs.push([
docItem.href,
docItem.innerText
])
}
}
}
var failureDocs = []
for(var docItem of docItems){
var docDetail = docItem.parentElement.parentElement
var docParent = docDetail.parentElement
if(docDetail.className.indexOf('table_body notice') == -1){
if(Number(docParent.getElementsByClassName('recomd')[0].innerText) == 0){
if(!isBad(docItem.innerText)){
failureDocs.push([
docItem.href,
docItem.innerText
])
}
}
}
}
return {
docs: collectedDocs,
failure: failureDocs
}
})
// 추천내용을 수집합니다.
let addedNew = 0
// 추천에 실패한 목록을 추천 대기 목록에 추가시킵니다.
// logger(`탐색 중 추가로 발견한 미추천 게시글 수: ${Object.keys(collected.failure).length}`)
for(let collectedDocIndex in collected.failure){
let collectedDoc = collected.failure[collectedDocIndex]
// 추천하지 않기로 결정된 게시글이면 넘어갑니다.
if(ignoredCache.get(collectedDoc[0]) !== undefined)
continue
// 아직 추천을 진행하지 않은 게시글이라면 넘어갑니다.
if(votedCache.get(collectedDoc[0]) === undefined)
continue
// 현재 추천이 진행중인 게시글이라면 넘어갑니다.
if(typeof processingChucheonList[collectedDoc[0]] != 'undefined')
continue
// 추천이 한번이상 진행되었는데도
// 추천이 적용되지 않은 게시글만 포함시킵니다.
if((typeof preChucheonList[collectedDoc[0]]) === 'undefined'){
preChucheonList[collectedDoc[0]] = collectedDoc[1]
addedNew++
}
}
// 게시글 목록을 역순으로 추천 대기 목록에 추가합니다.
for(let collectedDocIndex in collected.docs){
let collectedDoc = collected.docs[collectedDocIndex]
//console.log(collectedDocIndex, collectedDoc[0])
// 추천하지 않기로 결정된 게시글이면 넘어갑니다.
if(ignoredCache.get(collectedDoc[0]) !== undefined)
continue
if(votedCache.get(collectedDoc[0]) !== undefined)
continue
// 현재 추천이 진행중인 게시글이라면 넘어갑니다.
if(typeof processingChucheonList[collectedDoc[0]] != 'undefined')
continue
if((typeof preChucheonList[collectedDoc[0]]) === 'undefined'){
preChucheonList[collectedDoc[0]] = collectedDoc[1]
addedNew++
}
}
if(addedNew != 0)logger(`${addedNew} 건의 글이 새로 올라왔습니다.`)
}
async function collect(){
await refreshPage.goto('https://bbs.ruliweb.com/community/board/300143', {waitUntil: 'domcontentloaded'})
// 최신게시글 데이터를 모두 수집합니다.
await collectBoardFresh(refreshPage)
}
// await collect() // 임시용 await
collectInterval = setInterval(collect, 10000)
async function hit(){
let isStartedReboot = false
try{
let keys = Object.keys(preChucheonList)
if(keys.length == 0) return
// 해당 게시글을 추천예정 목록에서 삭제합니다.
let targetUrl = keys.shift()
let targetName = preChucheonList[targetUrl]
delete preChucheonList[targetUrl]
// 이후 추천 진행중 목록에 추가합니다.
processingChucheonList[targetUrl] = true
const hitPage = await browser.newPage()
let work = async (page)=>{
// 추천할 게시글 페이지로 갑니다.
await page.goto(targetUrl, {
waitUntil: 'domcontentloaded'
})
// 최신게시글 데이터를 모두 수집합니다.
await collectBoardFresh(page)
// 모든 내용을 승인합니다.
page.on('dialog', async dialog => {
await dialog.accept()
})
// 게시글을 추천합니다.
let pageResult = await page.evaluate(() => {
function isBad(text){
for(var bad of bads){
var badCount = 0
var badChars = bad.split('')
if(typeof ignores[bad] != 'undefined'){
for(var ignoreWord of ignores[bad]){
if(text.indexOf(ignoreWord) != -1)
return false
}
}
for(var badChar of badChars){
if(text.indexOf(badChar) != -1)
badCount++
}
if(badCount == badChars.length)
return true
}
return false
}
if(isBad(document.querySelector('.board_main_view').querySelector('.view_content').innerText))
return false
document.querySelector('.board_main_view').querySelector('.like').click()
return true
})
if(!pageResult)
ignoredCache.set(targetUrl, true)
}
//await pool.run(work)
await work(hitPage)
hitPage.close()
logger(`추천완료. ${Object.keys(preChucheonList).length} 개 남음. 제목: ${targetName}`)
// 추천한 목록에 추가합니다.
votedCache.set(targetUrl, true)
// 추천을 진행 중인 목록에서 삭제합니다.
delete processingChucheonList[targetUrl]
}catch(e){
browserErrorCount++
if(browserErrorCount < 5){
logger(`작업 진행 도중 오류가 발생했습니다. 자동 재시작까지 남은 오류수 ${browserErrorCount}`)
}else{
if(isStartedReboot) return
isStartedReboot = true
logger(`브라우저 5회 오류 발생으로 인해 시스템을 재시작합니다...`)
if(collectInterval !== null)
clearInterval(collectInterval)
if(hitInterval !== null)
clearInterval(hitInterval)
await browser.close()
console.error('reboot')
process.exit(1)
}
//console.log(e)
}
}
//await hit()
hitInterval = setInterval(hit, 2500)
/*
const dimensions = await page.evaluate(() => {
return {
width: document.documentElement.clientWidth,
height: document.documentElement.clientHeight,
deviceScaleFactor: window.devicePixelRatio
};
});
*/
// await browser.close()
});
chucheon()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment