cron.go 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533
  1. package controller
  2. import (
  3. "crawler/config"
  4. "crawler/model"
  5. "crawler/service"
  6. "crawler/utility"
  7. "fmt"
  8. "github.com/gin-gonic/gin"
  9. "github.com/gocolly/colly"
  10. "log"
  11. "net"
  12. "net/http"
  13. "os"
  14. "strconv"
  15. "time"
  16. )
  17. type CronController interface {
  18. List(c *gin.Context)
  19. Info(c *gin.Context)
  20. Detail(c *gin.Context)
  21. GetKey(c *gin.Context) string
  22. SetLastPage(page int)
  23. GetLastPage() int
  24. }
  25. type Cron struct {
  26. MovieListModel model.MovieListModel
  27. MovieInfoModel model.MovieInfoModel
  28. MovieDetailModel model.MovieDetailModel
  29. MovieStatsModel model.MovieStatsModel
  30. Kobis model.Kobis
  31. Rest service.Rest
  32. }
  33. /**
  34. * 영화진흥위원회 영화 목록
  35. */
  36. func (this *Cron) List(c *gin.Context) {
  37. var (
  38. start = time.Now()
  39. page = GetLastPage()
  40. perPage = 100
  41. total, errors, insertRows, updateRows = 0, 0, 0, 0
  42. output = func(n ...int) string {
  43. s := fmt.Sprintf("Total : %d\n", n[0])
  44. s += fmt.Sprintf("Error: %d\n", n[1])
  45. s += fmt.Sprintf("InsertRows: %d\n", n[2])
  46. s += fmt.Sprintf("UpdateRows: %d\n", n[3])
  47. s += fmt.Sprintf("소요시간: %f초\n", time.Since(start).Seconds())
  48. return s
  49. }
  50. key = GetKey(c)
  51. )
  52. //defer func() {
  53. // if r := recover(); r != nil {
  54. // msg := fmt.Sprintf("[영화 목록 수집 오류 발생]\n")
  55. // msg += output(total, errors, insertRows, updateRows)
  56. // utility.SendMessage(msg)
  57. // }
  58. //}()
  59. for {
  60. var (
  61. req = this.MovieListModel.SearchMovieListParams
  62. insertData, updateData []model.MovieListInfo
  63. )
  64. req.Key = key
  65. req.CurPage = page
  66. req.ItemPerPage = perPage
  67. data, err := this.Kobis.MovieListAPI(req)
  68. if err != nil {
  69. c.JSON(http.StatusBadRequest, err.Error())
  70. break
  71. }
  72. // 더 이상 값이 없다면 중지
  73. if data.MovieListResult.TotCnt <= 0 {
  74. errors++
  75. break
  76. }
  77. // 입력할 값과 수정할 값 구분
  78. for _, row := range data.MovieListResult.MovieList {
  79. if this.MovieListModel.IsExists(row.MovieCd) == true {
  80. updateData = append(updateData, row)
  81. updateRows++
  82. } else {
  83. insertData = append(insertData, row)
  84. insertRows++
  85. }
  86. total++
  87. }
  88. if insertRows > 0 {
  89. if err = this.MovieListModel.Insert(insertData); err != nil {
  90. errors++
  91. }
  92. }
  93. if updateRows > 0 {
  94. if err = this.MovieListModel.Update(updateData); err != nil {
  95. errors++
  96. }
  97. }
  98. fmt.Println(output(total, errors, insertRows, updateRows))
  99. SetLastPage(page)
  100. page++
  101. }
  102. //msg := "[영화 목록 수집 종료]\n"
  103. //msg += output(total, errors, insertRows, updateRows)
  104. //utility.SendMessage(msg)
  105. c.JSON(http.StatusOK, gin.H{
  106. "total": total,
  107. "errors": errors,
  108. "page": page,
  109. "perPage": perPage,
  110. "insertRows": insertRows,
  111. "updateRows": updateRows,
  112. })
  113. }
  114. /**
  115. * 영화진흥위원회 영화 기본 정보
  116. */
  117. func (this *Cron) Info(c *gin.Context) {
  118. var (
  119. start = time.Now()
  120. codes = this.MovieListModel.MovieInfoExcludeCodes()
  121. total, errors, insertRows, updateRows = 0, 0, 0, 0
  122. output = func(n ...int) string {
  123. s := fmt.Sprintf("Total : %d\n", n[0])
  124. s += fmt.Sprintf("Error: %d\n", n[1])
  125. s += fmt.Sprintf("InsertRows: %d\n", n[2])
  126. s += fmt.Sprintf("UpdateRows: %d\n", n[3])
  127. s += fmt.Sprintf("소요시간: %f초\n", time.Since(start).Seconds())
  128. return s
  129. }
  130. key = GetKey(c)
  131. )
  132. //defer func() {
  133. // if r := recover(); r != nil {
  134. // msg := fmt.Sprintf("[영화 기본 정보 수집 오류 발생]\n")
  135. // msg += output(total, errors, insertRows, updateRows)
  136. // utility.SendMessage(msg)
  137. // }
  138. //}()
  139. for _, movieCd := range codes {
  140. var (
  141. req = this.MovieInfoModel.SearchMovieInfoParams
  142. )
  143. req.Key = key
  144. req.MovieCd = movieCd
  145. data, err := this.Kobis.MovieInfoAPI(req)
  146. if err != nil {
  147. c.JSON(http.StatusBadRequest, err.Error())
  148. break
  149. }
  150. row := data.MovieInfoResult.MovieInfo
  151. if row.MovieCd == "" {
  152. errors++
  153. break
  154. }
  155. if this.MovieInfoModel.IsExists(row.MovieCd) == true {
  156. if err = this.MovieInfoModel.Update(row); err == nil {
  157. updateRows++
  158. } else {
  159. errors++
  160. }
  161. } else {
  162. if err = this.MovieInfoModel.Insert(row); err == nil {
  163. insertRows++
  164. } else {
  165. errors++
  166. }
  167. }
  168. fmt.Println(output(total, errors, insertRows, updateRows))
  169. total++
  170. }
  171. //msg := "[영화 기본 정보 수집 종료]\n"
  172. //msg += output(total, errors, insertRows, updateRows)
  173. //utility.SendMessage(msg)
  174. c.JSON(http.StatusOK, gin.H{
  175. "total": total,
  176. "error": errors,
  177. "insertRows": insertRows,
  178. "updateRows": updateRows,
  179. })
  180. }
  181. /*
  182. * 호출 순서
  183. * OnRequest -> OnError -> OnResponseHeaders -> OnResponse -> OnHTML -> OnXML -> OnScraped
  184. */
  185. func (this *Cron) Detail(c *gin.Context) {
  186. var (
  187. start = time.Now()
  188. codes = this.MovieListModel.MovieDetailExcludeCodes()
  189. total, scraped, errors, insertRows, updateRows, target = 0, 0, 0, 0, 0, len(codes)
  190. output = func(n ...int) string {
  191. s := fmt.Sprintf("Total : %d\n", n[0])
  192. s += fmt.Sprintf("Scraped: %d\n", n[1])
  193. s += fmt.Sprintf("Error: %d\n", n[2])
  194. s += fmt.Sprintf("InsertRows: %d\n", n[3])
  195. s += fmt.Sprintf("UpdateRows: %d\n", n[4])
  196. s += fmt.Sprintf("Target : %d\n", n[5])
  197. s += fmt.Sprintf("소요시간: %f초\n", time.Since(start).Seconds())
  198. return s
  199. }
  200. c1 = colly.NewCollector(
  201. colly.AllowedDomains(config.KOBIS_DOMAIN),
  202. colly.IgnoreRobotsTxt(),
  203. colly.Async(false),
  204. )
  205. )
  206. c1.WithTransport(&http.Transport{
  207. DialContext: (&net.Dialer{
  208. Timeout: 30 * time.Second,
  209. KeepAlive: 30 * time.Second,
  210. }).DialContext,
  211. MaxIdleConns: 0,
  212. MaxIdleConnsPerHost: 100,
  213. IdleConnTimeout: 30 * time.Second,
  214. TLSHandshakeTimeout: 30 * time.Second,
  215. ExpectContinueTimeout: 30 * time.Second,
  216. DisableCompression: false,
  217. })
  218. //var c2 = c1.Clone()
  219. c1.OnRequest(func(r *colly.Request) {
  220. r.Headers.Set("User-Agent", utility.RandomString())
  221. r.Headers.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
  222. })
  223. c1.OnError(func(_ *colly.Response, err error) {
  224. log.Printf("Error(c1) : %s\n", err.Error())
  225. errors++
  226. })
  227. c1.OnScraped(func(r *colly.Response) {
  228. scraped++
  229. })
  230. /*
  231. 관객 수, 누적 매출액 조회
  232. */
  233. //c2.OnRequest(func(r *colly.Request) {
  234. // r.Headers.Set("User-Agent", utility.RandomString())
  235. // r.Headers.Set("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8")
  236. //})
  237. //
  238. //c2.OnError(func(_ *colly.Response, err error) {
  239. // log.Printf("Error(c2) : %s\n", err.Error())
  240. // errors++
  241. //})
  242. //
  243. //c2.OnScraped(func(r *colly.Response) {
  244. // scraped++
  245. //})
  246. // 복구처리
  247. defer func() {
  248. if e := recover(); e != nil {
  249. //msg := "[영화 상세 정보 수집 오류 발생]\n"
  250. //msg += output(total, scraped, errors, insertRows, updateRows, len(codes))
  251. //utility.SendMessage(msg)
  252. }
  253. }()
  254. for i, movieCd := range codes {
  255. if movieCd == "" {
  256. continue
  257. }
  258. movieDetail := this.MovieDetailModel.MovieDetail
  259. movieDetail.MovieCd = movieCd
  260. c1.OnHTML(".item_tab.basic", func(e *colly.HTMLElement) {
  261. var host = config.KOBIS_HOST
  262. movieDetail.MainImg = e.ChildAttr("a.fl.thumb", "href")
  263. if movieDetail.MainImg != "" && movieDetail.MainImg != "#" {
  264. movieDetail.MainImg = host + movieDetail.MainImg
  265. }
  266. movieDetail.ThumbImg = e.ChildAttr("a.fl.thumb > img", "src")
  267. if movieDetail.ThumbImg != "" && movieDetail.ThumbImg != "#" {
  268. movieDetail.ThumbImg = host + movieDetail.ThumbImg
  269. }
  270. movieDetail.Synopsis = e.ChildText("div.info.info2 p.desc_info")
  271. e.ForEach("div#post > input", func(_ int, ee *colly.HTMLElement) {
  272. movieDetail.Poster = append(movieDetail.Poster, model.Poster{
  273. Thumb: host + ee.Attr("thn_img"),
  274. Origin: host + ee.Attr("img"),
  275. })
  276. })
  277. e.ForEach("div#stl > input", func(_ int, ee *colly.HTMLElement) {
  278. movieDetail.StillCut = append(movieDetail.StillCut, model.StillCut{
  279. Thumb: host + ee.Attr("thn_img"),
  280. Origin: host + ee.Attr("img"),
  281. })
  282. })
  283. })
  284. //c2.OnHTML("body", func(e *colly.HTMLElement) {
  285. // var (
  286. // tr = e.DOM.Find(".info").Eq(0).Find("table tbody tr").Eq(1)
  287. // saleAcc = utility.RemoveSpecialChar(strings.Replace(tr.Find("td").Eq(2).Text(), "(100%)", "", 1))
  288. // audiAcc = utility.RemoveSpecialChar(strings.Replace(tr.Find("td").Eq(3).Text(), "(100%)", "", 1))
  289. // )
  290. // SaleAcc, _ := strconv.Atoi(saleAcc)
  291. // AudiAcc, _ := strconv.Atoi(audiAcc)
  292. //
  293. // movieDetail.SaleAcc = SaleAcc
  294. // movieDetail.AudiAcc = AudiAcc
  295. //})
  296. err := c1.Post(config.MOVIE_DETAIL, map[string]string{
  297. "code": movieCd,
  298. "sType": "",
  299. "titleYN": "Y",
  300. "etcParam": "",
  301. "isOuterReq": "false",
  302. })
  303. if err != nil {
  304. errors++
  305. continue
  306. }
  307. //if this.Rest.Check(err) {
  308. // errors++
  309. // continue
  310. //}
  311. //err = c2.Post(config.MOVIE_DETAIL, map[string]string{
  312. // "code": movieCd,
  313. // "sType": "stat",
  314. //})
  315. //if err != nil {
  316. // errors++
  317. // continue
  318. //}
  319. //
  320. //if this.Rest.Check(err) {
  321. // errors++
  322. // continue
  323. //}
  324. if this.MovieDetailModel.IsExists(movieCd) == true {
  325. if err = this.MovieDetailModel.Update(movieDetail); err == nil {
  326. updateRows++
  327. } else {
  328. errors++
  329. }
  330. } else {
  331. if err = this.MovieDetailModel.Insert(movieDetail); err == nil {
  332. insertRows++
  333. } else {
  334. errors++
  335. }
  336. }
  337. fmt.Println(output(total, scraped, errors, insertRows, updateRows, target))
  338. codes[i] = ""
  339. target--
  340. total++
  341. }
  342. //msg := "[영화 상세 정보 수집 종료]\n"
  343. //msg += output(total, scraped, insertRows, updateRows, errors)
  344. //utility.SendMessage(msg)
  345. c.JSON(http.StatusOK, gin.H{
  346. "total": total,
  347. "insertRows": insertRows,
  348. "updateRows": updateRows,
  349. })
  350. }
  351. /**
  352. * 영화진흥위원회 박스오피스 (통계 조회)
  353. */
  354. /*
  355. func (this *Cron) Stats(c *gin.Context) {
  356. var (
  357. start = time.Now()
  358. total, errors, insertRows, updateRows, page = 0, 0, 0, 0, 1
  359. output = func(n ...int) string {
  360. s := fmt.Sprintf("Total : %d\n", n[0])
  361. s += fmt.Sprintf("Error: %d\n", n[1])
  362. s += fmt.Sprintf("InsertRows: %d\n", n[2])
  363. s += fmt.Sprintf("UpdateRows: %d\n", n[3])
  364. s += fmt.Sprintf("Page: %d\n", n[4])
  365. s += fmt.Sprintf("소요시간: %f초\n", time.Since(start).Seconds())
  366. return s
  367. }
  368. )
  369. req := this.MovieStatsModel.SearchBoxOfficeParams
  370. req.ServiceKey = config.Env.Movie.Kcisa.BoxOfficeKey
  371. req.NumOfRows = 2000
  372. req.PageNo = 1
  373. for {
  374. var (
  375. insertData = make([]model.BoxOfficeInfo, 0)
  376. updateData = make([]model.BoxOfficeInfo, 0)
  377. )
  378. req.PageNo = page
  379. data, err := this.Kobis.MovieBoxOfficeAPI(req)
  380. if err != nil {
  381. c.JSON(http.StatusBadRequest, err.Error())
  382. break
  383. }
  384. list := data.Response.Body.Items.Item
  385. if len(list) <= 0 {
  386. break
  387. }
  388. for _, row := range list {
  389. query, err := url.ParseQuery(row.Url)
  390. if err != nil {
  391. errors++
  392. continue
  393. }
  394. movieCd := query.Get("dtCd")
  395. if this.MovieStatsModel.IsExists(movieCd) == true {
  396. updateData = append(updateData, row)
  397. updateRows++
  398. } else {
  399. insertData = append(insertData, row)
  400. insertRows++
  401. }
  402. }
  403. if insertRows > 0 {
  404. if err = this.MovieStatsModel.Insert(insertData); err != nil {
  405. errors++
  406. }
  407. }
  408. if updateRows > 0 {
  409. if err = this.MovieStatsModel.Update(updateData); err != nil {
  410. errors++
  411. }
  412. }
  413. fmt.Println(output(total, errors, insertRows, updateRows, page))
  414. page++
  415. total++
  416. }
  417. msg := "[영화 통계 정보 수집 종료]\n"
  418. msg += output(total, errors, insertRows, updateRows, page)
  419. utility.SendMessage(msg)
  420. c.JSON(http.StatusOK, gin.H{
  421. "total": total,
  422. "error": errors,
  423. "insertRows": insertRows,
  424. "updateRows": updateRows,
  425. "page": page,
  426. })
  427. }
  428. */
  429. func GetKey(c *gin.Context) string {
  430. if c.Query("key") == "1" {
  431. return config.Movie.Kobis.ApiKey_1
  432. } else if c.Query("key") == "2" {
  433. return config.Movie.Kobis.ApiKey_2
  434. } else {
  435. return "f5eef3421c602c6cb7ea224104795888"
  436. }
  437. }
  438. // 마지막 호출 Page 저장
  439. func SetLastPage(page int) {
  440. data, err := os.Create(config.LAST_PAGE_PATH_KOBIS)
  441. if err != nil {
  442. fmt.Println(err)
  443. }
  444. defer func() {
  445. if data.Close() != nil {
  446. fmt.Println(err)
  447. }
  448. }()
  449. _, _ = data.WriteString(strconv.FormatInt(int64(page), 10))
  450. fmt.Printf("Set last page: %d\n", page)
  451. }
  452. // 마지막 호출 Page 조회
  453. func GetLastPage() int {
  454. byte, err := os.ReadFile(config.LAST_PAGE_PATH_KOBIS)
  455. if err != nil {
  456. fmt.Println(err)
  457. }
  458. page, _ := strconv.Atoi(string(byte))
  459. if page == 0 {
  460. page = 1
  461. }
  462. return page
  463. }