w-dwdata-hub 1.0.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.
Files changed (135) hide show
  1. package/.editorconfig +9 -0
  2. package/.eslintignore +3 -0
  3. package/.eslintrc.js +55 -0
  4. package/.jsdoc +25 -0
  5. package/LICENSE +21 -0
  6. package/README.md +168 -0
  7. package/SECURITY.md +5 -0
  8. package/babel.config.js +16 -0
  9. package/dist/w-dwdata-hub.umd.js +7 -0
  10. package/dist/w-dwdata-hub.umd.js.map +1 -0
  11. package/docs/WDwdataHub.mjs.html +136 -0
  12. package/docs/decodeEntities.mjs.html +129 -0
  13. package/docs/extractDivContent.mjs.html +147 -0
  14. package/docs/fetchAiNewsAggregator.mjs.html +155 -0
  15. package/docs/fetchAisixiang.mjs.html +1038 -0
  16. package/docs/fetchCnyes.mjs.html +260 -0
  17. package/docs/fetchGuancha.mjs.html +1007 -0
  18. package/docs/fetchHackerNews.mjs.html +189 -0
  19. package/docs/fetchMoneydj.mjs.html +251 -0
  20. package/docs/fetchMops.mjs.html +385 -0
  21. package/docs/fetchRSS.mjs.html +160 -0
  22. package/docs/fetchStatementdog.mjs.html +213 -0
  23. package/docs/fetchTaifex.mjs.html +513 -0
  24. package/docs/fetchTpex3insti.mjs.html +278 -0
  25. package/docs/fetchTpexMargin.mjs.html +248 -0
  26. package/docs/fetchTpexStock.mjs.html +199 -0
  27. package/docs/fetchTwDataHoliday.mjs.html +236 -0
  28. package/docs/fetchTwseMargin.mjs.html +253 -0
  29. package/docs/fetchTwseStock.mjs.html +188 -0
  30. package/docs/fetchTwseT86.mjs.html +208 -0
  31. package/docs/fetchWithRetry.mjs.html +376 -0
  32. package/docs/fonts/Montserrat/Montserrat-Bold.eot +0 -0
  33. package/docs/fonts/Montserrat/Montserrat-Bold.ttf +0 -0
  34. package/docs/fonts/Montserrat/Montserrat-Bold.woff +0 -0
  35. package/docs/fonts/Montserrat/Montserrat-Bold.woff2 +0 -0
  36. package/docs/fonts/Montserrat/Montserrat-Regular.eot +0 -0
  37. package/docs/fonts/Montserrat/Montserrat-Regular.ttf +0 -0
  38. package/docs/fonts/Montserrat/Montserrat-Regular.woff +0 -0
  39. package/docs/fonts/Montserrat/Montserrat-Regular.woff2 +0 -0
  40. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.eot +0 -0
  41. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.svg +978 -0
  42. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.ttf +0 -0
  43. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff +0 -0
  44. package/docs/fonts/Source-Sans-Pro/sourcesanspro-light-webfont.woff2 +0 -0
  45. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.eot +0 -0
  46. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.svg +1049 -0
  47. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.ttf +0 -0
  48. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff +0 -0
  49. package/docs/fonts/Source-Sans-Pro/sourcesanspro-regular-webfont.woff2 +0 -0
  50. package/docs/getOptFetch.mjs.html +185 -0
  51. package/docs/global.html +22649 -0
  52. package/docs/htmlToMarkdown.mjs.html +168 -0
  53. package/docs/index.html +84 -0
  54. package/docs/isYmd.mjs.html +114 -0
  55. package/docs/parseIntComma.mjs.html +100 -0
  56. package/docs/safeFilename.mjs.html +102 -0
  57. package/docs/scripts/collapse.js +39 -0
  58. package/docs/scripts/commonNav.js +28 -0
  59. package/docs/scripts/linenumber.js +25 -0
  60. package/docs/scripts/nav.js +12 -0
  61. package/docs/scripts/polyfill.js +4 -0
  62. package/docs/scripts/prettify/Apache-License-2.0.txt +202 -0
  63. package/docs/scripts/prettify/lang-css.js +2 -0
  64. package/docs/scripts/prettify/prettify.js +28 -0
  65. package/docs/scripts/search.js +99 -0
  66. package/docs/styles/jsdoc.css +776 -0
  67. package/docs/styles/prettify.css +80 -0
  68. package/docs/toDatetimeUTC8.mjs.html +143 -0
  69. package/docs/toRocDate.mjs.html +107 -0
  70. package/g.mjs +73 -0
  71. package/package.json +31 -0
  72. package/script.txt +17 -0
  73. package/src/WDwdataHub.mjs +64 -0
  74. package/src/decodeEntities.mjs +57 -0
  75. package/src/extractDivContent.mjs +75 -0
  76. package/src/fetchAiNewsAggregator.mjs +83 -0
  77. package/src/fetchAisixiang.mjs +966 -0
  78. package/src/fetchCnyes.mjs +188 -0
  79. package/src/fetchGuancha.mjs +935 -0
  80. package/src/fetchHackerNews.mjs +117 -0
  81. package/src/fetchMoneydj.mjs +179 -0
  82. package/src/fetchMops.mjs +313 -0
  83. package/src/fetchRSS.mjs +88 -0
  84. package/src/fetchStatementdog.mjs +141 -0
  85. package/src/fetchTaifex.mjs +441 -0
  86. package/src/fetchTpex3insti.mjs +206 -0
  87. package/src/fetchTpexMargin.mjs +176 -0
  88. package/src/fetchTpexStock.mjs +127 -0
  89. package/src/fetchTwDataHoliday.mjs +164 -0
  90. package/src/fetchTwseMargin.mjs +181 -0
  91. package/src/fetchTwseStock.mjs +116 -0
  92. package/src/fetchTwseT86.mjs +136 -0
  93. package/src/fetchWithRetry.mjs +304 -0
  94. package/src/getOptFetch.mjs +113 -0
  95. package/src/htmlToMarkdown.mjs +96 -0
  96. package/src/isYmd.mjs +42 -0
  97. package/src/parseIntComma.mjs +28 -0
  98. package/src/safeFilename.mjs +30 -0
  99. package/src/toDatetimeUTC8.mjs +71 -0
  100. package/src/toRocDate.mjs +35 -0
  101. package/test/WDwdataHub.test.mjs +63 -0
  102. package/test/decodeEntities.test.mjs +42 -0
  103. package/test/extractDivContent.test.mjs +48 -0
  104. package/test/fetchAiNewsAggregator.test.mjs +79 -0
  105. package/test/fetchAisixiang.test.mjs +225 -0
  106. package/test/fetchCnyes.test.mjs +61 -0
  107. package/test/fetchGuancha.test.mjs +190 -0
  108. package/test/fetchHackerNews.test.mjs +78 -0
  109. package/test/fetchMoneydj.test.mjs +82 -0
  110. package/test/fetchMops.test.mjs +106 -0
  111. package/test/fetchRSS.test.mjs +85 -0
  112. package/test/fetchStatementdog.test.mjs +69 -0
  113. package/test/fetchTaifex.test.mjs +99 -0
  114. package/test/fetchTpex3insti.test.mjs +90 -0
  115. package/test/fetchTpexMargin.test.mjs +94 -0
  116. package/test/fetchTpexStock.test.mjs +68 -0
  117. package/test/fetchTwDataHoliday.test.mjs +100 -0
  118. package/test/fetchTwseMargin.test.mjs +81 -0
  119. package/test/fetchTwseStock.test.mjs +80 -0
  120. package/test/fetchTwseT86.test.mjs +76 -0
  121. package/test/fetchWithRetry.test.mjs +174 -0
  122. package/test/htmlToMarkdown.test.mjs +71 -0
  123. package/test/isYmd.test.mjs +56 -0
  124. package/test/parseIntComma.test.mjs +38 -0
  125. package/test/safeFilename.test.mjs +35 -0
  126. package/test/toDatetimeUTC8.test.mjs +65 -0
  127. package/test/toRocDate.test.mjs +38 -0
  128. package/test/tools/encodeBig5.mjs +80 -0
  129. package/test/tools/fixtures.mjs +539 -0
  130. package/test/tools/hasChrome.mjs +39 -0
  131. package/test/tools/serverForTest.mjs +397 -0
  132. package/toolg/addVersion.mjs +4 -0
  133. package/toolg/cleanFolder.mjs +5 -0
  134. package/toolg/gDistRollup.mjs +49 -0
  135. package/toolg/modifyReadme.mjs +4 -0
@@ -0,0 +1,188 @@
1
+ import get from 'lodash-es/get.js'
2
+ import isestr from 'wsemi/src/isestr.mjs'
3
+ import isearr from 'wsemi/src/isearr.mjs'
4
+ import ispint from 'wsemi/src/ispint.mjs'
5
+ import isp0int from 'wsemi/src/isp0int.mjs'
6
+ import cint from 'wsemi/src/cint.mjs'
7
+ import delay from 'wsemi/src/delay.mjs'
8
+ import fetchWithRetry from './fetchWithRetry.mjs'
9
+ import getOptFetch from './getOptFetch.mjs'
10
+ import toDatetimeUTC8 from './toDatetimeUTC8.mjs'
11
+
12
+
13
+ //鉅亨網(Anue)台股新聞列表API
14
+ let API_URL = 'https://api.cnyes.com/media/api/v1/newslist/category/tw_stock'
15
+
16
+
17
+ //鉅亨網新聞內容頁網址前綴
18
+ let NEWS_URL = 'https://news.cnyes.com/news/id'
19
+
20
+
21
+ //本函數之預設抓取設定
22
+ let DFLT = {
23
+ timeout: 30000,
24
+ maxRetries: 10,
25
+ baseDelayMs: 5000,
26
+ maxDelayMs: 30000,
27
+ retryStatus: [403],
28
+ label: 'fetch-cnyes',
29
+ }
30
+
31
+
32
+ //預設值
33
+ let DEFAULT_PAGE_DELAY_MS = 500
34
+ let DEFAULT_TARGET_TOTAL = 100
35
+ let DEFAULT_PAGE_LIMIT = 30
36
+ let DEFAULT_DAYS_BACK = 10
37
+
38
+
39
+ /**
40
+ * 抓取鉅亨網台股新聞
41
+ *
42
+ * 逐頁抓取鉅亨網tw_stock分類新聞直至達到targetTotal或無更多資料, 頁間預設間隔500毫秒以降低限流風險;
43
+ * 時間欄位一律轉為UTC+8之'YYYY-MM-DD HH:mm:ss'字串, 單筆publishAt缺漏或異常時該筆time退回空字串並記錄, 不影響其餘新聞
44
+ *
45
+ * @param {Object} [opt={}] 輸入設定物件,預設{}
46
+ * @param {String} [opt.url='https://api.cnyes.com/media/api/v1/newslist/category/tw_stock'] 輸入新聞列表API網址字串,供測試或改指向鏡像時覆寫
47
+ * @param {Integer} [opt.targetTotal=100] 輸入目標新聞則數正整數,預設100
48
+ * @param {Integer} [opt.pageLimit=30] 輸入單頁請求則數正整數,預設30
49
+ * @param {Integer} [opt.daysBack=10] 輸入回溯天數正整數,預設10
50
+ * @param {Integer} [opt.pageDelayMs=500] 輸入頁間延遲毫秒整數,預設500
51
+ * @param {Integer} [opt.timeout=30000] 輸入單次請求逾時毫秒整數,預設30000
52
+ * @param {Integer} [opt.maxRetries=10] 輸入最大重試次數整數,含初始共執行maxRetries+1次,預設10
53
+ * @param {Integer} [opt.baseDelayMs=5000] 輸入重試之線性退避基礎毫秒整數,預設5000
54
+ * @param {Integer} [opt.maxDelayMs=30000] 輸入重試之線性退避上限毫秒整數,預設30000
55
+ * @param {Boolean} [opt.showLog=true] 輸入是否顯示過程訊息布林值,預設true
56
+ * @returns {Promise} 回傳Promise,resolve回傳新聞物件陣列,各物件為{time,title,link},抓取失敗時reject回傳錯誤物件
57
+ * @example
58
+ *
59
+ * import fetchCnyes from './src/fetchCnyes.mjs'
60
+ *
61
+ * let test = async () => {
62
+ *
63
+ * let rs = await fetchCnyes({ targetTotal: 30 })
64
+ * console.log(rs.length, rs[0])
65
+ * // => 30 { time: '2026-08-07 09:02:03', title: '...', link: 'https://news.cnyes.com/news/id/1234567' }
66
+ *
67
+ * }
68
+ * await test()
69
+ * .catch((err) => {
70
+ * console.log(err)
71
+ * })
72
+ *
73
+ */
74
+ async function fetchCnyes(opt = {}) {
75
+
76
+ //url
77
+ let url = get(opt, 'url')
78
+ if (!isestr(url)) {
79
+ url = API_URL
80
+ }
81
+
82
+ //targetTotal
83
+ let targetTotal = get(opt, 'targetTotal')
84
+ if (!ispint(targetTotal)) {
85
+ targetTotal = DEFAULT_TARGET_TOTAL
86
+ }
87
+ else {
88
+ targetTotal = cint(targetTotal)
89
+ }
90
+
91
+ //pageLimit
92
+ let pageLimit = get(opt, 'pageLimit')
93
+ if (!ispint(pageLimit)) {
94
+ pageLimit = DEFAULT_PAGE_LIMIT
95
+ }
96
+ else {
97
+ pageLimit = cint(pageLimit)
98
+ }
99
+
100
+ //daysBack
101
+ let daysBack = get(opt, 'daysBack')
102
+ if (!ispint(daysBack)) {
103
+ daysBack = DEFAULT_DAYS_BACK
104
+ }
105
+ else {
106
+ daysBack = cint(daysBack)
107
+ }
108
+
109
+ //pageDelayMs
110
+ let pageDelayMs = get(opt, 'pageDelayMs')
111
+ if (!isp0int(pageDelayMs)) {
112
+ pageDelayMs = DEFAULT_PAGE_DELAY_MS
113
+ }
114
+ else {
115
+ pageDelayMs = cint(pageDelayMs)
116
+ }
117
+
118
+ //optFetch
119
+ let optFetch = getOptFetch(opt, DFLT)
120
+ let showLog = optFetch.showLog
121
+
122
+ //now, startAt
123
+ let now = Math.floor(Date.now() / 1000)
124
+ let startAt = now - 86400 * daysBack
125
+
126
+ if (showLog) {
127
+ console.log('Starting to fetch Anue (tw_stock) news...')
128
+ }
129
+
130
+ let allItems = []
131
+ let page = 1
132
+ while (allItems.length < targetTotal) {
133
+
134
+ //qs
135
+ let qs = new URLSearchParams({
136
+ page: String(page),
137
+ limit: String(pageLimit),
138
+ isCategoryHeadline: '1',
139
+ startAt: String(startAt),
140
+ endAt: String(now),
141
+ })
142
+
143
+ //data
144
+ let data = await fetchWithRetry(`${url}?${qs.toString()}`, optFetch)
145
+
146
+ //items
147
+ let items = get(data, 'items.data')
148
+ if (!isearr(items)) {
149
+ if (showLog) {
150
+ console.log('No more items found.')
151
+ }
152
+ break
153
+ }
154
+
155
+ allItems = allItems.concat(items)
156
+ if (showLog) {
157
+ console.log(`Page ${page}: Fetched ${items.length} items. Total so far: ${allItems.length}`)
158
+ }
159
+ page++
160
+
161
+ await delay(pageDelayMs)
162
+
163
+ }
164
+
165
+ //finalItems
166
+ let finalItems = allItems.slice(0, targetTotal)
167
+ if (showLog) {
168
+ console.log(`Total items collected: ${finalItems.length}`)
169
+ }
170
+
171
+ return finalItems.map((item) => {
172
+
173
+ //time, 單筆publishAt缺漏或異常時退回空字串並記錄, 保留title與link避免拖垮其餘正常新聞
174
+ let time = toDatetimeUTC8(get(item, 'publishAt', 0))
175
+ if (time === '' && showLog) {
176
+ console.warn(`[fetch-cnyes] 時間格式化失敗(newsId=${get(item, 'newsId', '')}, publishAt=${get(item, 'publishAt', '')}) — 該筆time退回空字串`)
177
+ }
178
+
179
+ return {
180
+ time,
181
+ title: get(item, 'title', ''),
182
+ link: `${NEWS_URL}/${get(item, 'newsId', '')}`,
183
+ }
184
+ })
185
+ }
186
+
187
+
188
+ export default fetchCnyes