@heripo/research-radar 1.0.5 β 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -2
- package/dist/index.cjs +157 -0
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +157 -0
- package/dist/index.js.map +1 -1
- package/package.json +12 -8
package/README.md
CHANGED
|
@@ -23,7 +23,7 @@ An AI-powered newsletter service for Korean cultural heritage. Built on [`@llm-n
|
|
|
23
23
|
**Technical highlights**:
|
|
24
24
|
- Type-safe TypeScript with strict interfaces
|
|
25
25
|
- Provider pattern for swapping components (Crawling/Analysis/Content/Email)
|
|
26
|
-
-
|
|
26
|
+
- 66 crawling targets across heritage agencies, museums, academic societies
|
|
27
27
|
- LLM-driven analysis (GPT-5 models)
|
|
28
28
|
- Built-in retries, chain options, preview emails
|
|
29
29
|
|
|
@@ -131,7 +131,7 @@ Uses the **Provider-Service pattern** from `@llm-newsletter-kit/core`. See [core
|
|
|
131
131
|
|
|
132
132
|
**Config** (`src/config/`): Brand, language, LLM settings
|
|
133
133
|
|
|
134
|
-
**Targets** (`src/config/crawling-targets.ts`):
|
|
134
|
+
**Targets** (`src/config/crawling-targets.ts`): 66 sources (News 52, Business 4, Employment 10)
|
|
135
135
|
|
|
136
136
|
**Parsers** (`src/parsers/`): Custom extractors per organization
|
|
137
137
|
|
|
@@ -152,6 +152,21 @@ npm run typecheck # TypeScript type-check
|
|
|
152
152
|
npm run format # Prettier formatting
|
|
153
153
|
```
|
|
154
154
|
|
|
155
|
+
### Crawler Debugger
|
|
156
|
+
|
|
157
|
+
A web-based tool for testing crawling parsers during development. Built with Express.js and vanilla HTML/CSS/JS to minimize dependencies.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
npm run dev:crawler # Start at http://localhost:3333
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Features**:
|
|
164
|
+
- Test `parseList()` and `parseDetail()` parsers via web UI
|
|
165
|
+
- View raw HTML source for debugging
|
|
166
|
+
- Copy parsed results as JSON
|
|
167
|
+
- 5-minute response cache (with skip/clear options)
|
|
168
|
+
- Timing info for fetch and parse operations
|
|
169
|
+
|
|
155
170
|
## π€ Contributing
|
|
156
171
|
|
|
157
172
|
You can use this project in two ways:
|
package/dist/index.cjs
CHANGED
|
@@ -970,6 +970,135 @@ function getUniqIdFromNrichMajorEvent(element) {
|
|
|
970
970
|
return ((element.attr('onclick') ?? '').match(/fnViewPage\('(.*)'\)/)?.[1] ?? '');
|
|
971
971
|
}
|
|
972
972
|
|
|
973
|
+
const LIST_API_URL = 'http://www.yngogo.or.kr/module/ntt/unity/selectNttListAjax.ink';
|
|
974
|
+
const DETAIL_API_URL = 'http://www.yngogo.or.kr/module/ntt/unity/selectNttDetailAjax.ink';
|
|
975
|
+
const BASE_URL = 'http://www.yngogo.or.kr';
|
|
976
|
+
// User-Agent list used by real browsers
|
|
977
|
+
const USER_AGENTS = [
|
|
978
|
+
// Windows - Chrome, Edge, Firefox
|
|
979
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
980
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
|
981
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
982
|
+
// macOS - Chrome, Safari, Firefox
|
|
983
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
984
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
|
|
985
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
986
|
+
// Linux - Chrome, Firefox
|
|
987
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
988
|
+
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
989
|
+
// Additional common combinations
|
|
990
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
|
991
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
|
992
|
+
];
|
|
993
|
+
// Pick a random User-Agent
|
|
994
|
+
const getRandomUserAgent = () => USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
|
995
|
+
/**
|
|
996
|
+
* Parse list page from μλ¨κ³ κ³ νν (Yeongnam Archaeological Society)
|
|
997
|
+
* Uses internal API since the table is rendered via CSR
|
|
998
|
+
* @see https://www.yngogo.or.kr
|
|
999
|
+
*/
|
|
1000
|
+
const parseYngogoList = async (_html, menuSeq, bbsSeq, sitecntntsSeq) => {
|
|
1001
|
+
// Fetch from internal API (CSR workaround)
|
|
1002
|
+
const response = await fetch(LIST_API_URL, {
|
|
1003
|
+
method: 'POST',
|
|
1004
|
+
headers: {
|
|
1005
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
1006
|
+
'User-Agent': getRandomUserAgent(),
|
|
1007
|
+
},
|
|
1008
|
+
body: new URLSearchParams({
|
|
1009
|
+
siteSeq: '32000001030',
|
|
1010
|
+
bbsSeq,
|
|
1011
|
+
pageIndex: '1',
|
|
1012
|
+
menuSeq,
|
|
1013
|
+
pageMode: 'B',
|
|
1014
|
+
sitecntntsSeq,
|
|
1015
|
+
tabTyCode: 'dataManage',
|
|
1016
|
+
mngrAt: 'N',
|
|
1017
|
+
searchCondition: '',
|
|
1018
|
+
searchKeyword: '',
|
|
1019
|
+
nttSeq: '',
|
|
1020
|
+
}),
|
|
1021
|
+
});
|
|
1022
|
+
const html = await response.text();
|
|
1023
|
+
const $ = cheerio__namespace.load(html);
|
|
1024
|
+
const posts = [];
|
|
1025
|
+
$('.basic-table01 tr').each((_index, element) => {
|
|
1026
|
+
const columns = $(element).find('td');
|
|
1027
|
+
if (columns.length === 0) {
|
|
1028
|
+
return;
|
|
1029
|
+
}
|
|
1030
|
+
const titleElement = columns.eq(1).find('a');
|
|
1031
|
+
const uniqId = getUniqId(titleElement);
|
|
1032
|
+
const detailUrl = `${BASE_URL}/subList/${menuSeq}?pmode=detail&nttSeq=${uniqId}&bbsSeq=${bbsSeq}&sitecntntsSeq=${sitecntntsSeq}`;
|
|
1033
|
+
const title = titleElement.text()?.trim() ?? '';
|
|
1034
|
+
const date = getDate(columns.eq(3).text().trim());
|
|
1035
|
+
posts.push({
|
|
1036
|
+
uniqId,
|
|
1037
|
+
title,
|
|
1038
|
+
date,
|
|
1039
|
+
detailUrl: cleanUrl(detailUrl),
|
|
1040
|
+
dateType: core.DateType.REGISTERED,
|
|
1041
|
+
});
|
|
1042
|
+
});
|
|
1043
|
+
return posts;
|
|
1044
|
+
};
|
|
1045
|
+
/**
|
|
1046
|
+
* Parse detail page from μλ¨κ³ κ³ νν (Yeongnam Archaeological Society)
|
|
1047
|
+
* Uses internal API since the detail page is rendered via CSR
|
|
1048
|
+
*/
|
|
1049
|
+
const parseYngogoDetail = async (_html, menuSeq, bbsSeq, nttSeq, sitecntntsSeq) => {
|
|
1050
|
+
// Fetch from internal API (CSR workaround)
|
|
1051
|
+
const response = await fetch(DETAIL_API_URL, {
|
|
1052
|
+
method: 'POST',
|
|
1053
|
+
headers: {
|
|
1054
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
1055
|
+
'User-Agent': getRandomUserAgent(),
|
|
1056
|
+
},
|
|
1057
|
+
body: new URLSearchParams({
|
|
1058
|
+
siteSeq: '32000001030',
|
|
1059
|
+
bbsSeq,
|
|
1060
|
+
nttSeq,
|
|
1061
|
+
pageIndex: '1',
|
|
1062
|
+
ordrSe: 'D',
|
|
1063
|
+
searchCnd: 'frstRegistPnttm',
|
|
1064
|
+
checkNttSeq: '',
|
|
1065
|
+
menuSeq,
|
|
1066
|
+
mngrAt: 'N',
|
|
1067
|
+
parntsNttSeq: '',
|
|
1068
|
+
secretAt: '',
|
|
1069
|
+
searchAt: '',
|
|
1070
|
+
sitecntntsSeq,
|
|
1071
|
+
cmntUseAt: 'N',
|
|
1072
|
+
atchFilePosblAt: 'Y',
|
|
1073
|
+
atchFilePosblCo: '3',
|
|
1074
|
+
listCount: '10',
|
|
1075
|
+
searchCondition: '1',
|
|
1076
|
+
searchKeyword: '',
|
|
1077
|
+
}),
|
|
1078
|
+
});
|
|
1079
|
+
const html = await response.text();
|
|
1080
|
+
const $ = cheerio__namespace.load(html);
|
|
1081
|
+
const content = $('.conM_txt');
|
|
1082
|
+
return {
|
|
1083
|
+
detailContent: new TurndownService().turndown(content.html() ?? ''),
|
|
1084
|
+
hasAttachedFile: $('#atchFile_div').length > 0,
|
|
1085
|
+
hasAttachedImage: content.find('img').length > 0,
|
|
1086
|
+
};
|
|
1087
|
+
};
|
|
1088
|
+
function getUniqId(element) {
|
|
1089
|
+
// fnView('1005200642', 'admin', '', '',''); - extract first param
|
|
1090
|
+
return (element.attr('onclick') ?? '').match(/fnView\('([^']*)'/)?.[1] ?? '';
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* Extract nttSeq from CSR page HTML
|
|
1094
|
+
* The URL query parameter is embedded in the page script
|
|
1095
|
+
*/
|
|
1096
|
+
function extractNttSeq(html) {
|
|
1097
|
+
// Pattern: nttSeq=1005200644 or nttSeq='1005200644'
|
|
1098
|
+
const match = html.match(/nttSeq[=:]['"]?(\d+)/);
|
|
1099
|
+
return match?.[1] ?? '';
|
|
1100
|
+
}
|
|
1101
|
+
|
|
973
1102
|
const crawlingTargetGroups = [
|
|
974
1103
|
{
|
|
975
1104
|
id: 'news',
|
|
@@ -1200,6 +1329,27 @@ const crawlingTargetGroups = [
|
|
|
1200
1329
|
parseList: parseHsasList,
|
|
1201
1330
|
parseDetail: parseHsasDetail,
|
|
1202
1331
|
},
|
|
1332
|
+
{
|
|
1333
|
+
id: 'μλ¨κ³ κ³ νν_곡μ§μ¬ν',
|
|
1334
|
+
name: 'μλ¨κ³ κ³ νν 곡μ§μ¬ν',
|
|
1335
|
+
url: 'http://www.yngogo.or.kr/subList/32000001120',
|
|
1336
|
+
parseList: (html) => parseYngogoList(html, '32000001120', '32000001157', '32000001711'),
|
|
1337
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001120', '32000001157', extractNttSeq(html), '32000001711'),
|
|
1338
|
+
},
|
|
1339
|
+
{
|
|
1340
|
+
id: 'μλ¨κ³ κ³ νν_νκ³μμ',
|
|
1341
|
+
name: 'μλ¨κ³ κ³ νν νκ³μμ',
|
|
1342
|
+
url: 'http://www.yngogo.or.kr/subList/32000001133',
|
|
1343
|
+
parseList: (html) => parseYngogoList(html, '32000001133', '32000001161', '32000001715'),
|
|
1344
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001133', '32000001161', extractNttSeq(html), '32000001715'),
|
|
1345
|
+
},
|
|
1346
|
+
{
|
|
1347
|
+
id: 'μλ¨κ³ κ³ νν_νμ₯μμ',
|
|
1348
|
+
name: 'μλ¨κ³ κ³ νν νμ₯μμ',
|
|
1349
|
+
url: 'http://www.yngogo.or.kr/subList/32000001135',
|
|
1350
|
+
parseList: (html) => parseYngogoList(html, '32000001135', '32000001163', '32000001717'),
|
|
1351
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001135', '32000001163', extractNttSeq(html), '32000001717'),
|
|
1352
|
+
},
|
|
1203
1353
|
{
|
|
1204
1354
|
id: 'κ΅λ¦½μ€μλ°λ¬Όκ΄_μλ¦Ό',
|
|
1205
1355
|
name: 'κ΅λ¦½μ€μλ°λ¬Όκ΄ μλ¦Ό',
|
|
@@ -1385,6 +1535,13 @@ const crawlingTargetGroups = [
|
|
|
1385
1535
|
parseList: parseKaahList,
|
|
1386
1536
|
parseDetail: parseKaahDetail,
|
|
1387
1537
|
},
|
|
1538
|
+
{
|
|
1539
|
+
id: 'μλ¨κ³ κ³ νν_μ±μ©κ³΅κ³ ',
|
|
1540
|
+
name: 'μλ¨κ³ κ³ νν μ±μ©κ³΅κ³ ',
|
|
1541
|
+
url: 'http://www.yngogo.or.kr/subList/32000001136',
|
|
1542
|
+
parseList: (html) => parseYngogoList(html, '32000001136', '32000001164', '32000001718'),
|
|
1543
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001136', '32000001164', extractNttSeq(html), '32000001718'),
|
|
1544
|
+
},
|
|
1388
1545
|
{
|
|
1389
1546
|
id: 'κ΅λ¦½μ€μλ°λ¬Όκ΄_μ±μ©μλ΄',
|
|
1390
1547
|
name: 'κ΅λ¦½μ€μλ°λ¬Όκ΄ μ±μ© μλ΄',
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.cjs","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/dist/index.js
CHANGED
|
@@ -949,6 +949,135 @@ function getUniqIdFromNrichMajorEvent(element) {
|
|
|
949
949
|
return ((element.attr('onclick') ?? '').match(/fnViewPage\('(.*)'\)/)?.[1] ?? '');
|
|
950
950
|
}
|
|
951
951
|
|
|
952
|
+
const LIST_API_URL = 'http://www.yngogo.or.kr/module/ntt/unity/selectNttListAjax.ink';
|
|
953
|
+
const DETAIL_API_URL = 'http://www.yngogo.or.kr/module/ntt/unity/selectNttDetailAjax.ink';
|
|
954
|
+
const BASE_URL = 'http://www.yngogo.or.kr';
|
|
955
|
+
// User-Agent list used by real browsers
|
|
956
|
+
const USER_AGENTS = [
|
|
957
|
+
// Windows - Chrome, Edge, Firefox
|
|
958
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
959
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36 Edg/124.0.0.0',
|
|
960
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
961
|
+
// macOS - Chrome, Safari, Firefox
|
|
962
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
963
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/17.5 Safari/605.1.15',
|
|
964
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
965
|
+
// Linux - Chrome, Firefox
|
|
966
|
+
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36',
|
|
967
|
+
'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:126.0) Gecko/20100101 Firefox/126.0',
|
|
968
|
+
// Additional common combinations
|
|
969
|
+
'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36',
|
|
970
|
+
'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36',
|
|
971
|
+
];
|
|
972
|
+
// Pick a random User-Agent
|
|
973
|
+
const getRandomUserAgent = () => USER_AGENTS[Math.floor(Math.random() * USER_AGENTS.length)];
|
|
974
|
+
/**
|
|
975
|
+
* Parse list page from μλ¨κ³ κ³ νν (Yeongnam Archaeological Society)
|
|
976
|
+
* Uses internal API since the table is rendered via CSR
|
|
977
|
+
* @see https://www.yngogo.or.kr
|
|
978
|
+
*/
|
|
979
|
+
const parseYngogoList = async (_html, menuSeq, bbsSeq, sitecntntsSeq) => {
|
|
980
|
+
// Fetch from internal API (CSR workaround)
|
|
981
|
+
const response = await fetch(LIST_API_URL, {
|
|
982
|
+
method: 'POST',
|
|
983
|
+
headers: {
|
|
984
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
985
|
+
'User-Agent': getRandomUserAgent(),
|
|
986
|
+
},
|
|
987
|
+
body: new URLSearchParams({
|
|
988
|
+
siteSeq: '32000001030',
|
|
989
|
+
bbsSeq,
|
|
990
|
+
pageIndex: '1',
|
|
991
|
+
menuSeq,
|
|
992
|
+
pageMode: 'B',
|
|
993
|
+
sitecntntsSeq,
|
|
994
|
+
tabTyCode: 'dataManage',
|
|
995
|
+
mngrAt: 'N',
|
|
996
|
+
searchCondition: '',
|
|
997
|
+
searchKeyword: '',
|
|
998
|
+
nttSeq: '',
|
|
999
|
+
}),
|
|
1000
|
+
});
|
|
1001
|
+
const html = await response.text();
|
|
1002
|
+
const $ = cheerio.load(html);
|
|
1003
|
+
const posts = [];
|
|
1004
|
+
$('.basic-table01 tr').each((_index, element) => {
|
|
1005
|
+
const columns = $(element).find('td');
|
|
1006
|
+
if (columns.length === 0) {
|
|
1007
|
+
return;
|
|
1008
|
+
}
|
|
1009
|
+
const titleElement = columns.eq(1).find('a');
|
|
1010
|
+
const uniqId = getUniqId(titleElement);
|
|
1011
|
+
const detailUrl = `${BASE_URL}/subList/${menuSeq}?pmode=detail&nttSeq=${uniqId}&bbsSeq=${bbsSeq}&sitecntntsSeq=${sitecntntsSeq}`;
|
|
1012
|
+
const title = titleElement.text()?.trim() ?? '';
|
|
1013
|
+
const date = getDate(columns.eq(3).text().trim());
|
|
1014
|
+
posts.push({
|
|
1015
|
+
uniqId,
|
|
1016
|
+
title,
|
|
1017
|
+
date,
|
|
1018
|
+
detailUrl: cleanUrl(detailUrl),
|
|
1019
|
+
dateType: DateType.REGISTERED,
|
|
1020
|
+
});
|
|
1021
|
+
});
|
|
1022
|
+
return posts;
|
|
1023
|
+
};
|
|
1024
|
+
/**
|
|
1025
|
+
* Parse detail page from μλ¨κ³ κ³ νν (Yeongnam Archaeological Society)
|
|
1026
|
+
* Uses internal API since the detail page is rendered via CSR
|
|
1027
|
+
*/
|
|
1028
|
+
const parseYngogoDetail = async (_html, menuSeq, bbsSeq, nttSeq, sitecntntsSeq) => {
|
|
1029
|
+
// Fetch from internal API (CSR workaround)
|
|
1030
|
+
const response = await fetch(DETAIL_API_URL, {
|
|
1031
|
+
method: 'POST',
|
|
1032
|
+
headers: {
|
|
1033
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
1034
|
+
'User-Agent': getRandomUserAgent(),
|
|
1035
|
+
},
|
|
1036
|
+
body: new URLSearchParams({
|
|
1037
|
+
siteSeq: '32000001030',
|
|
1038
|
+
bbsSeq,
|
|
1039
|
+
nttSeq,
|
|
1040
|
+
pageIndex: '1',
|
|
1041
|
+
ordrSe: 'D',
|
|
1042
|
+
searchCnd: 'frstRegistPnttm',
|
|
1043
|
+
checkNttSeq: '',
|
|
1044
|
+
menuSeq,
|
|
1045
|
+
mngrAt: 'N',
|
|
1046
|
+
parntsNttSeq: '',
|
|
1047
|
+
secretAt: '',
|
|
1048
|
+
searchAt: '',
|
|
1049
|
+
sitecntntsSeq,
|
|
1050
|
+
cmntUseAt: 'N',
|
|
1051
|
+
atchFilePosblAt: 'Y',
|
|
1052
|
+
atchFilePosblCo: '3',
|
|
1053
|
+
listCount: '10',
|
|
1054
|
+
searchCondition: '1',
|
|
1055
|
+
searchKeyword: '',
|
|
1056
|
+
}),
|
|
1057
|
+
});
|
|
1058
|
+
const html = await response.text();
|
|
1059
|
+
const $ = cheerio.load(html);
|
|
1060
|
+
const content = $('.conM_txt');
|
|
1061
|
+
return {
|
|
1062
|
+
detailContent: new TurndownService().turndown(content.html() ?? ''),
|
|
1063
|
+
hasAttachedFile: $('#atchFile_div').length > 0,
|
|
1064
|
+
hasAttachedImage: content.find('img').length > 0,
|
|
1065
|
+
};
|
|
1066
|
+
};
|
|
1067
|
+
function getUniqId(element) {
|
|
1068
|
+
// fnView('1005200642', 'admin', '', '',''); - extract first param
|
|
1069
|
+
return (element.attr('onclick') ?? '').match(/fnView\('([^']*)'/)?.[1] ?? '';
|
|
1070
|
+
}
|
|
1071
|
+
/**
|
|
1072
|
+
* Extract nttSeq from CSR page HTML
|
|
1073
|
+
* The URL query parameter is embedded in the page script
|
|
1074
|
+
*/
|
|
1075
|
+
function extractNttSeq(html) {
|
|
1076
|
+
// Pattern: nttSeq=1005200644 or nttSeq='1005200644'
|
|
1077
|
+
const match = html.match(/nttSeq[=:]['"]?(\d+)/);
|
|
1078
|
+
return match?.[1] ?? '';
|
|
1079
|
+
}
|
|
1080
|
+
|
|
952
1081
|
const crawlingTargetGroups = [
|
|
953
1082
|
{
|
|
954
1083
|
id: 'news',
|
|
@@ -1179,6 +1308,27 @@ const crawlingTargetGroups = [
|
|
|
1179
1308
|
parseList: parseHsasList,
|
|
1180
1309
|
parseDetail: parseHsasDetail,
|
|
1181
1310
|
},
|
|
1311
|
+
{
|
|
1312
|
+
id: 'μλ¨κ³ κ³ νν_곡μ§μ¬ν',
|
|
1313
|
+
name: 'μλ¨κ³ κ³ νν 곡μ§μ¬ν',
|
|
1314
|
+
url: 'http://www.yngogo.or.kr/subList/32000001120',
|
|
1315
|
+
parseList: (html) => parseYngogoList(html, '32000001120', '32000001157', '32000001711'),
|
|
1316
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001120', '32000001157', extractNttSeq(html), '32000001711'),
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
id: 'μλ¨κ³ κ³ νν_νκ³μμ',
|
|
1320
|
+
name: 'μλ¨κ³ κ³ νν νκ³μμ',
|
|
1321
|
+
url: 'http://www.yngogo.or.kr/subList/32000001133',
|
|
1322
|
+
parseList: (html) => parseYngogoList(html, '32000001133', '32000001161', '32000001715'),
|
|
1323
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001133', '32000001161', extractNttSeq(html), '32000001715'),
|
|
1324
|
+
},
|
|
1325
|
+
{
|
|
1326
|
+
id: 'μλ¨κ³ κ³ νν_νμ₯μμ',
|
|
1327
|
+
name: 'μλ¨κ³ κ³ νν νμ₯μμ',
|
|
1328
|
+
url: 'http://www.yngogo.or.kr/subList/32000001135',
|
|
1329
|
+
parseList: (html) => parseYngogoList(html, '32000001135', '32000001163', '32000001717'),
|
|
1330
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001135', '32000001163', extractNttSeq(html), '32000001717'),
|
|
1331
|
+
},
|
|
1182
1332
|
{
|
|
1183
1333
|
id: 'κ΅λ¦½μ€μλ°λ¬Όκ΄_μλ¦Ό',
|
|
1184
1334
|
name: 'κ΅λ¦½μ€μλ°λ¬Όκ΄ μλ¦Ό',
|
|
@@ -1364,6 +1514,13 @@ const crawlingTargetGroups = [
|
|
|
1364
1514
|
parseList: parseKaahList,
|
|
1365
1515
|
parseDetail: parseKaahDetail,
|
|
1366
1516
|
},
|
|
1517
|
+
{
|
|
1518
|
+
id: 'μλ¨κ³ κ³ νν_μ±μ©κ³΅κ³ ',
|
|
1519
|
+
name: 'μλ¨κ³ κ³ νν μ±μ©κ³΅κ³ ',
|
|
1520
|
+
url: 'http://www.yngogo.or.kr/subList/32000001136',
|
|
1521
|
+
parseList: (html) => parseYngogoList(html, '32000001136', '32000001164', '32000001718'),
|
|
1522
|
+
parseDetail: (html) => parseYngogoDetail(html, '32000001136', '32000001164', extractNttSeq(html), '32000001718'),
|
|
1523
|
+
},
|
|
1367
1524
|
{
|
|
1368
1525
|
id: 'κ΅λ¦½μ€μλ°λ¬Όκ΄_μ±μ©μλ΄',
|
|
1369
1526
|
name: 'κ΅λ¦½μ€μλ°λ¬Όκ΄ μ±μ© μλ΄',
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@heripo/research-radar",
|
|
3
3
|
"private": false,
|
|
4
4
|
"type": "module",
|
|
5
|
-
"version": "1.0
|
|
5
|
+
"version": "1.2.0",
|
|
6
6
|
"description": "AI-driven intelligence for Korean cultural heritage. This package serves as both a ready-to-use newsletter service and a practical implementation example for the LLM-Newsletter-Kit.",
|
|
7
7
|
"main": "dist/index.cjs",
|
|
8
8
|
"module": "dist/index.js",
|
|
@@ -36,33 +36,37 @@
|
|
|
36
36
|
"lint:fix": "eslint --fix ./src",
|
|
37
37
|
"lint:ci": "eslint --quiet ./src",
|
|
38
38
|
"typecheck": "tsc --noEmit",
|
|
39
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md,mdx}\""
|
|
39
|
+
"format": "prettier --write \"src/**/*.{ts,tsx,js,jsx,json,css,md,mdx}\"",
|
|
40
|
+
"dev:crawler": "tsx watch dev-tools/crawler-debugger/server.ts"
|
|
40
41
|
},
|
|
41
42
|
"author": "kimhongyeon",
|
|
42
43
|
"license": "Apache-2.0",
|
|
43
44
|
"dependencies": {
|
|
44
|
-
"@ai-sdk/openai": "^
|
|
45
|
+
"@ai-sdk/openai": "^3.0.7",
|
|
45
46
|
"cheerio": "^1.1.2",
|
|
46
47
|
"turndown": "^7.2.2"
|
|
47
48
|
},
|
|
48
49
|
"peerDependencies": {
|
|
49
|
-
"@llm-newsletter-kit/core": "
|
|
50
|
+
"@llm-newsletter-kit/core": "~1.1.0"
|
|
50
51
|
},
|
|
51
52
|
"devDependencies": {
|
|
52
53
|
"@eslint/js": "^9.39.2",
|
|
53
|
-
"@llm-newsletter-kit/core": "^1.0
|
|
54
|
-
"@trivago/prettier-plugin-sort-imports": "^6.0.
|
|
54
|
+
"@llm-newsletter-kit/core": "^1.1.0",
|
|
55
|
+
"@trivago/prettier-plugin-sort-imports": "^6.0.1",
|
|
56
|
+
"@types/express": "^5.0.6",
|
|
55
57
|
"@types/node": "^25.0.3",
|
|
56
58
|
"@types/turndown": "^5.0.6",
|
|
57
59
|
"eslint": "^9.39.2",
|
|
58
60
|
"eslint-plugin-unused-imports": "^4.3.0",
|
|
61
|
+
"express": "^5.2.1",
|
|
59
62
|
"prettier": "^3.7.4",
|
|
60
63
|
"rimraf": "^6.1.2",
|
|
61
|
-
"rollup": "^4.
|
|
64
|
+
"rollup": "^4.55.1",
|
|
62
65
|
"rollup-plugin-dts": "^6.3.0",
|
|
63
66
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
67
|
+
"tsx": "^4.21.0",
|
|
64
68
|
"typescript": "^5.9.3",
|
|
65
|
-
"typescript-eslint": "^8.
|
|
69
|
+
"typescript-eslint": "^8.52.0"
|
|
66
70
|
},
|
|
67
71
|
"repository": {
|
|
68
72
|
"type": "git",
|