kone
ainovel

ainovel

69 사이트 누락화수 타겟 다운로더 유저스크립트 / 검도제일선 누락화수 원본 / 텍스트 병합 파이썬

03/04/2026, 23:49:33
유틸
7315 views · 11 likes

Tampermonkey
입니다



// ==UserScript==

// @name         69shuba 통합 타겟 크롤러

// @version      8.0

// @description  모든 69shuba 소설 페이지에서 타겟 화수 입력 후 순차 크롤링

// @author       Gemini

// @match        https://www.69shuba.com/book/*

// @match        https://www.69shuba.com/txt/*

// @grant        none

// ==/UserScript==


(function() {

'use strict';


// UI 생성

const panel = document.createElement('div');

panel.style.cssText = 'position:fixed; top:20px; right:20px; z-index:9999; background:#fff; border:2px solid #333; padding:15px; border-radius:8px; width:220px;';

panel.innerHTML = `

<h4 style="margin:0 0 10px 0;">소설 크롤러</h4>

<input id="chList" type="text" placeholder="예: 6, 7, 8 또는 6 7 8" style="width:100%; margin-bottom:5px;">

<input id="delay" type="number" value="3.0" step="0.5" style="width:50px;">초 간격<br>

<button id="btnStart" style="margin-top:10px; width:100%;">시작하기</button>

`;

document.body.appendChild(panel);


document.getElementById('btnStart').onclick = () => {

const raw = document.getElementById('chList').value;

const delay = parseFloat(document.getElementById('delay').value) * 1000;

// 쉼표나 공백으로 숫자 추출

const targetNumbers = raw.split(/[\s,]+/).filter(x => x !== "").map(Number);


if (targetNumbers.length === 0) return alert('화수를 입력하세요');


localStorage.setItem('target_chapters', JSON.stringify(targetNumbers));

localStorage.setItem('crawl_delay', delay);

localStorage.setItem('crawl_status', 'running');

localStorage.setItem('novel_data', '');


location.reload();

};


// 크롤링 로직

if (localStorage.getItem('crawl_status') === 'running') {

if (window.location.href.includes('/book/')) {

const queue = [];

const targetNumbers = JSON.parse(localStorage.getItem('target_chapters'));

document.querySelectorAll('#catalog ul li a').forEach(a => {

const match = a.innerText.match(/第(\d+)章/);

if (match && targetNumbers.includes(parseInt(match[1]))) queue.push(a.href);

});

localStorage.setItem('crawler_queue', JSON.stringify(queue));

localStorage.setItem('crawler_index', 0);

window.location.href = queue[0];

}

else if (window.location.href.includes('/txt/')) {

const delay = parseInt(localStorage.getItem('crawl_delay'));

setTimeout(() => {

const title = document.querySelector('h1')?.innerText || '제목없음';

const content = document.querySelector('.txtnav')?.innerText || '';

let data = localStorage.getItem('novel_data') || '';

localStorage.setItem('novel_data', data + `\n\n=== ${title} ===\n\n${content}`);


let queue = JSON.parse(localStorage.getItem('crawler_queue') || '[]');

let index = parseInt(localStorage.getItem('crawler_index') || 0);


if (index < queue.length - 1) {

localStorage.setItem('crawler_index', index + 1);

window.location.href = queue[index + 1];

} else {

const blob = new Blob([localStorage.getItem('novel_data')], {type:'text/plain'});

const a = document.createElement('a');

a.href = URL.createObjectURL(blob);

a.download = 'novel_export.txt';

a.click();

localStorage.clear();

alert('완료! 파일이 다운로드되었습니다.');

}

}, delay);

}

}

})();


검도 제일선 볼려는데 누락이 너무 많아서 다운받다가 꼴받아서 만듬;;

일단
6, 7, 8, 11, 19, 29, 31, 49, 93, 119, 123, 147, 156, 157, 208, 209, 210, 211, 260, 265, 266, 288, 289, 427, 502, 505, 517, 566, 568, 569, 570, 571, 572, 573, 574, 627, 630, 631, 632, 638, 639, 645, 699, 714, 767, 777, 811, 812, 839, 848, 905, 927, 928, 937, 938, 939, 940, 945, 968, 969, 984, 1030, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1088, 1091, 1092, 1093, 1096, 1139, 1173, 1174, 1175, 1192, 1193, 1194, 1195, 1196, 1213, 1251, 1252, 1255, 1311, 1361, 1363, 1368, 1398, 1419, 1441, 1469, 1476, 1477, 1485, 1518, 1538, 1558, 1567, 1629, 1630, 1840, 1841, 1887, 1888, 1905, 1906, 1907, 1913, 1942, 1943, 1944, 1960, 1999, 2000, 2001, 2002, 2003, 2032, 2033, 2074, 2125, 2195, 2211, 2222, 2223, 2316, 2405, 2430, 2463, 2470, 2475, 2544, 2641, 2751, 2810, 2843, 3046, 3053, 3082, 3137, 3176, 3205, 3237, 3247, 3282, 3316, 3370, 3388, 3549, 3672

화 누락파일 원본 받은거만 올려드림 필요하시면 각자 알아서 번역해서 보시면 될듯

aHR0cHM6Ly9raW8uYWMvYy9keDJTQWpmT1dkS0xuOVNwRGFVNVdi
1주/국룰


제([0-9]+(장|화).*) 형식 기준으로 텍스트 파일 두개 정렬해서 합치는 파이썬입니다

////

import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext
import re
import os
from collections import defaultdict

def read_file_with_detected_encoding(filepath):
"""파일 인코딩을 감지하여 텍스트를 읽어옵니다."""
try:
with open(filepath, 'rb') as f:
raw_data = f.read()
except Exception as e:
return None
if raw_data.startswith(b'\xff\xfe'): return raw_data[2:].decode('utf-16-le', errors='ignore')
if raw_data.startswith(b'\xfe\xff'): return raw_data[2:].decode('utf-16-be', errors='ignore')
if raw_data.startswith(b'\xef\xbb\xbf'): return raw_data[3:].decode('utf-8', errors='ignore')
for enc in ('utf-8', 'cp949', 'euc-kr', 'utf-16'):
try: return raw_data.decode(enc)
except UnicodeDecodeError: continue
return raw_data.decode('utf-8', errors='ignore')

def save_file_utf16_crlf(filepath, content):
"""결과를 UTF-16 LE BOM 형식으로 저장합니다."""
try:
fixed_content = content.replace('\r\n', '\n').replace('\r', '\n').replace('\n', '\r\n')
data = ('\ufeff' + fixed_content).encode('utf-16-le')
with open(filepath, 'wb') as f:
f.write(data)
return True, None
except Exception as e:
return False, str(e)

class ChapterMergerApp:
def __init__(self, root):
self.root = root
self.root.title("장 번호순 파일 병합기 (v1.2)")
self.root.geometry("600x550")
self.path_A = tk.StringVar()
self.path_B = tk.StringVar()
self.create_widgets()

def create_widgets(self):
main_frame = tk.Frame(self.root, padx=20, pady=20)
main_frame.pack(fill="both", expand=True)
tk.Label(main_frame, text="병합할 파일들을 선택하세요 (제n장 기준 정렬)", font=("Malgun Gothic", 12, "bold")).pack(pady=(0, 20))

f1 = tk.Frame(main_frame)
f1.pack(fill="x", pady=5)
tk.Label(f1, text="파일 1:").pack(side="left")
tk.Entry(f1, textvariable=self.path_A, width=50).pack(side="left", padx=5)
tk.Button(f1, text="찾기", command=lambda: self.path_A.set(filedialog.askopenfilename())).pack(side="left")

f2 = tk.Frame(main_frame)
f2.pack(fill="x", pady=5)
tk.Label(f2, text="파일 2:").pack(side="left")
tk.Entry(f2, textvariable=self.path_B, width=50).pack(side="left", padx=5)
tk.Button(f2, text="찾기", command=lambda: self.path_B.set(filedialog.askopenfilename())).pack(side="left")

tk.Button(main_frame, text="번호순 병합 실행", command=self.run_merge,
bg="#E0F7FA", font=("Malgun Gothic", 10, "bold"), height=2).pack(pady=20, fill="x")

self.log_area = scrolledtext.ScrolledText(main_frame, height=15, state="disabled", bg="#F8F8F8")
self.log_area.pack(fill="both", expand=True)

def log(self, msg):
self.log_area.config(state="normal")
self.log_area.insert(tk.END, msg + "\n")
self.log_area.see(tk.END)
self.log_area.config(state="disabled")

def parse_chapters(self, filepath):
content = read_file_with_detected_encoding(filepath)
if not content: return {}

# 줄바꿈 통일 (정규식 처리를 위해)
content = content.replace('\r\n', '\n').replace('\r', '\n')

# 제n장/화 패턴 분리
pattern = re.compile(r'(^제\s*\d+\s*(?:장|화|章|話).*)', re.MULTILINE)
parts = pattern.split(content)

chapter_dict = defaultdict(list)

# 서문 처리
if parts[0].strip():
chapter_dict[0].append(parts[0].strip() + "\n\n")

for i in range(1, len(parts), 2):
title = parts[i].strip()
body = parts[i+1].strip() if i+1 < len(parts) else ""

num_match = re.search(r'(\d+)', title)
num = int(num_match.group(1)) if num_match else 999999

# ★ 핵심 수정: 제목과 본문 사이에 빈 줄(\n\n) 삽입
formatted_chapter = f"{title}\n\n{body}"
chapter_dict[num].append(formatted_chapter)

return chapter_dict

def run_merge(self):
p1, p2 = self.path_A.get(), self.path_B.get()
if not p1 or not p2:
messagebox.showwarning("경고", "파일을 두 개 모두 선택해주세요.")
return

self.log(f"병합 시작...")
try:
data1 = self.parse_chapters(p1)
data2 = self.parse_chapters(p2)
all_numbers = sorted(list(set(data1.keys()) | set(data2.keys())))

final_content = []
for n in all_numbers:
segments = []
if n in data1: segments.extend(data1[n])
if n in data2: segments.extend(data2[n])

for s in segments:
text = s.strip()
if text:
# 각 장과 장 사이에도 빈 줄 추가
final_content.append(text + "\n\n")

save_path = os.path.splitext(p1)[0] + "_merged_ordered.txt"
success, err = save_file_utf16_crlf(save_path, "".join(final_content))

if success:
self.log(f"성공! 정렬된 파일 저장됨: {os.path.basename(save_path)}")
messagebox.showinfo("성공", f"병합 완료!\n저장위치: {save_path}")
else:
self.log(f"저장 실패: {err}")
except Exception as e:
self.log(f"오류: {e}")
messagebox.showerror("오류", str(e))

if __name__ == "__main__":
root = tk.Tk()
app = ChapterMergerApp(root)
root.mainloop()

11
1 comment
Sign in to comment
유틸 개추
Sign in to comment
[오버로드] 왕의 탄생을 맞이하라 1-473
[패러디]
respectbot
10/16/25 3648 30
종합만화 : 킬러퀸부터 시작 1~647 완결
[패러디]
라스트오리진으로 팔려간 댕라
10/16/25 2278 30
[AI 번역] 내 미친 아내도 회귀했다『凶猛道侣也重生了』 (1-557) 완결 [雨下的好大]
[선협]
greener-straper
10/16/25 3586 33
[다중] 나 홈랜더는 하고 싶은대로 한다 1-559 完
[패러디]
金日成綜合大學
10/15/25 2654 34
사람이 고무 세계에 떨어졌는데, 시작부터 아내가 생겼다人在高武,開局送老婆1-344
일반
Sorkim
10/15/25 2095 26
[AI번역] 종말:내 여자가 소모한 물자, 만 배로 돌려받는다 末世:女人消耗的物资,万倍返还 1-929장 完
[일반]
esfly123
10/15/25 2153 15
미국만화 지옥의 군주
[패러디]
sjw7whch8
10/15/25 2553 29
[나루토]닌자 세계 전체가 위기에빠졋는데 라이브스트리밍을 그만둘수있을까요 1-225
일반
quer99
10/15/25 1627 20
일상세계의 스탠드유저 1~509 완
[패러디]
라스트오리진으로 팔려간 댕라
10/14/25 3355 31
[마블] 마블에 막 도착하자마자, 시스템이 '맨 오브 스틸'을 보상으로 주었다1-361(完)
[패러디]
金日成綜合大學
10/14/25 3056 27
사람이 고무 세계에 떨어졌는데, 시작부터 아내가 생겼다人在高武,開局送老婆1-175
일반
Sorkim
10/13/25 2517 37
신화기원 나는 항성급 거수로 진화했다1~694 txt
[선협]
esfly123
10/13/25 2922 42
상옥궐(上玉阙) 1-328
일반
pobit99
10/13/25 2882 34
코난 시체수집가 1~3030
[패러디]
03594
10/13/25 4264 22
[AI 번역] 종말의 슈퍼마켓 하렘『末日超市后宫』 (1-40) + (외전 1) 완결 [작가 소녀의 꿈(少女梦)]
[야설]
greener-straper
10/13/25 3978 29
[나루토]닌자 세계 전체가 위기에빠졋는데 라이브스트리밍을 그만둘수있을까요 1-100
[패러디]
quer99
10/13/25 2095 24
나는 아포칼립스에 스위트룸이 있다 [我在末世有套房] (0001-1619 완결) 가독성 수정
[일반]
kone_l9
10/13/25 2495 46
[명탐정코난]명탐정 코난 세계의 스파이 일상 1-977
[패러디]
닉네임을뭐라고하냐
10/13/25 1851 23
[명탐정코난]사망 노트로 사람을 죽이는 건 코학적이지 않나? 1-499
[패러디]
닉네임을뭐라고하냐
10/13/25 2075 29
[명탐정 코난]코난 한량 암살자의 종합 만화 일상 1~614
[패러디]
닉네임을뭐라고하냐
10/13/25 1834 29
[코난](요청) 코난에서는 사람들이 뇌옵션 시스템을 가지고 있다 1-310 (연중)
[패러디]
ainovelai
10/12/25 1971 18
[나루토] 우치하 일족이 전멸한 것은 안목이 부족했기 때문이다 1-78
[패러디]
金日成綜合大學
10/12/25 1595 17
나루토 2개
[패러디]
03594
10/12/25 3377 22
[AI번역][패러디][다중]그들과의 죽음이 확실한 사랑 게임 1-542
[패러디]
weieilkk22
10/12/25 2333 28
내 2차원 환생이 어째서 괴담 미스터리지? 1~300?
[패러디]
라스트오리진으로 팔려간 댕라
10/12/25 2515 22
[나루토] 스쿠나의 인주력, 나 우치하1-194(完)
[패러디]
金日成綜合大學
10/12/25 1961 18
[나루토] 나는 암부에서 숨어 지내다 초카게가 되었다(火影:我在暗部苟成超影) 1-392
[패러디]
ppeerroo
10/12/25 1776 27
헌터×헌터에서 시작해 최고의 의지가 되다 1-272
[패러디]
koiyh
10/12/25 2055 20
언정 모음집
[언정]
koiyh
10/12/25 10260 37
[AI 번역] 종말의 네크로맨서, 미친 듯이 병력을 모은다 靱鯩艫餇繧輽繬疯狂屯兵 1-1122화(완)
[일반]
shakeshack
10/12/25 2569 20