CGV
영화 그 이상의 감동. CGV
미정 --> 10 --> D Day
www.cgv.co.kr
극장으로 가서 예약 버튼을 눌러 어떤 방식으로 동작하는지 살펴봅시다.
아무 곳이나 상관 없지만, 저는 cgv상봉으로 하겠습니다.

하단에 영화 시간표가 보입니다. 아무거나 눌러봅시다.

http://www.cgv.co.kr/ticket/?MOVIE_CD=20031748&MOVIE_CD_GROUP=20031633&PLAY_YMD=20230207&THEATER_CD=0046&PLAY_START_TM=2250&AREA_CD=13&SCREEN_CD=009

URI을 보면 제가 선택했던 관과 영화 정보가 GET방식으로 전달되는 걸 볼 수 있습니다. 한번 살펴 보겠습니다.
- MOVIE_CD
- MOVIE_CD_GROUP
- PLAY_YMD
- THEATER_CD
- PLAY_START_TM
- AREA_CD
- SCREEN_CD
로 구성된걸 볼 수 있습니다.
MOVIE_CD는 영화 코드, MOVIE_CD_GROUP 또한 영화를 구분하기 위한 코드 같습니다.
PLAY_YMD는 어떤 날 예매를 선택했는지
THEATER_CD와 AREA_CD는 어떤 관 영화를 선택했는지
PLAY_START_TM과 SCREEN_CD는 몇시 영화를 선택하고 어떤관에서 관람을 하는지에 관한 정보를 전달해주는 것 같습니다.
저장하거나 파싱해야할 정보가 너무 많아 다시 이전의 관 영화 선택화면으로 돌아가봅시다.
http://www.cgv.co.kr/theaters/?areacode=01&theaterCode=0046&date=20230207
THEATERCODE는 일치하지만 AREACODE는 01로 다른걸 확인할 수 있습니다.
왜 다를까요...?
소스코드 보단 영화관 정보를 보내주는 API가 있다면 그걸 활용하기 위해 F12를 눌러 NETWORK 탭으로 가서 뒤져봅시다.
iframe으로 추가 페이지를 띄어 api를 찾을수가 없습니다.
소스 코드로 가서 확인해야 할 것 같습니다.

해당 주소에 확인하는 예약 주소값이 다행히 들어있는 걸 확인할 수 있습니다.

쿼리로 넘겨주는 date값을 변경하면 날짜또한 변경됩니다. 영화관별로 존재하는 영화 시간표가 다르므로 해당 날짜에 대한 정보도 따로 가져와야 할 것 같습니다.
저장에 필요한 정보를 정리해 봅시다.
- 시간 - 2월8일 12:30분
- 남은 좌석 수 (저는 저장하지 않습니다.), 총 좌석 수
- 2d, 4dx - 어떤 영화 타입인지
- 5관 3관 - 관별로 영화 시간표가 다르기도 하고 관별로 크기가 달라 선호하는 관이 다를 수 있어 저장합니다.
- 개봉 일자 - 저는 영화명_개봉일자 (아바타_2023)과 같이 저장하므로 변경을 해줘야 합니다.
html을 뜯어 봅시다.
우선 영화 정보들은 각 영화별 col-times로 구분되어 있습니다.

그 내부에 type-hall로 구분되어 관별로 나눠 저장되 있습니다.
- 시간, 남은 좌석 수 - info-timetable > ul > li > a에 data-playstarttime, data-seatremaincnt로 저장 되어 있습니다.
- 영화 타입과 관, 총 좌석 수 - info-hall > ul > 첫번째 자식 = 2D, 두번째 자식 = 5관, 세번째 자식 = 총 좌석 수
- 개봉 일자 - info-movie > 마지막 i
아래 다른 영화를 보고 패턴이 동일한지 확인해 봅시다.



영화관 내부의 시간표 패턴분석은 끝난 것 같습니다.
코드로 작성해 데이터를 가져와 보도록 합시다.
23년 2월 27일 cgv상봉의 시간표 입니다.

영화가 굉장히 많습니다.
Jsoup을 사용할 때 단순히 common/shotimes/iframeTheater.aspx로만 접속하여 넘겨주면 데이터가 오지 않습니다.
Referer과 userAgent, header를 조금 추가해주셔야 작동하게 보안 패치가 적용된 것 같습니다.
override fun getShowTime(movieTheater: String): List<ShowTime> {
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=0046&date=20230227"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val showtimes = doc.getElementsByClass("col-times")
println(showtimes.size)
for (showtime in showtimes) {
val infoMovie = showtime.getElementsByClass("info-movie")
val movieName = infoMovie[0].getElementsByTag("a")[0].text()
val age = infoMovie[0].getElementsByTag("i")[0].text()
val categories = infoMovie[0].getElementsByTag("i")[1].text()
val runningTime = infoMovie[0].getElementsByTag("i")[2].text()
val comeOut = infoMovie[0].getElementsByTag("i")[3].text().replace("[^0-9]+".toRegex(), "")
println("${movieName} ${age} ${categories} ${runningTime} ${comeOut}")
}
return emptyList()
}

영화 정보들은 잘 넘어옵니다.
이제 영화별 영화관과 상영 정보를 가져오도록 해봅시다.
override fun getShowTime(movieTheater: String): List<ShowTime> {
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=0046&date=20230227"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val showtimes = doc.getElementsByClass("col-times")
println(showtimes.size)
for (showtime in showtimes) {
val infoMovie = showtime.getElementsByClass("info-movie")[0]
val movieName = infoMovie.getElementsByTag("a")[0].text()
val age = infoMovie.getElementsByTag("i")[0].text()
val categories = infoMovie.getElementsByTag("i")[1].text()
val runningTime = infoMovie.getElementsByTag("i")[2].text()
val comeOut = infoMovie.getElementsByTag("i")[3].text().replace("[^0-9]+".toRegex(), "")
println("${movieName} ${age} ${categories} ${runningTime} ${comeOut}")
val typeHalls = showtime.getElementsByClass("type-hall")
for (typeHall in typeHalls) {
val infoHall = typeHall.getElementsByClass("info-hall")[0]
val dimension = infoHall.getElementsByTag("li")[0].text()
val whereTheater = infoHall.getElementsByTag("li")[1].text()
val allSeats = infoHall.getElementsByTag("li")[2].text().replace("[^0-9]+".toRegex(), "")
println("${dimension} ${whereTheater} ${allSeats}")
val infoTimeTable = typeHall.getElementsByClass("info-timetable")[0]
val timeinfoes = infoTimeTable.getElementsByTag("li")
for (timeinfo in timeinfoes) {
val datas = timeinfo.getElementsByTag("a")[0]
val starttime = datas.attr("data-playstarttime")
val href = CGVurl + datas.attr("href")
val seatsLeft = datas.attr("data-seatremaincnt")
println("${starttime} ${href} ${seatsLeft}")
}
}
}
return emptyList()
}

데이터가 분류되서 잘 넘어옵니다.
그런데 만약 남아있는 모든 시간표가 마감인 경우 a 태그가 없습니다. 해당 사항만 따로 에러처리를 해줍시다.
for (timeinfo in timeinfoes) {
val datas = timeinfo.getElementsByTag("a")
var starttime = ""
var href = ""
var seatsLeft = ""
if (datas.isEmpty()) {
starttime = timeinfo.getElementsByTag("em")[0].text().replace(":", "")
seatsLeft = "마감"
} else {
starttime = datas[0].attr("data-playstarttime")
href = CGVurl + datas[0].attr("href")
seatsLeft = datas[0].attr("data-seatremaincnt")
}
println("${movieName} ${age}세 ${categories} ${runningTime}분 ${comeOut} ${dimension} ${whereTheater} ${allSeats} ${starttime} ${href} ${seatsLeft}좌석 남음")
}
이제 모든 영화관과 모든 날짜의 데이터가 필요하므로 해당 내용을 구해봅시다.
우선 영화관 (CGV상봉, CGV강남)과 같은 데이터들 먼저 구하기로 합시다.

sect-city 하위에 li 태그로 분리되어 감싸져 있고 바로 아래 a태그에 지역이 area 클래스 하단 a 태그의 class혹은 내용에 관 이름이 들어가 있습니다. 그런데 페이지를 요청해서 보면 코드에 안들어 있습니다.
요청 들어온 페이지를 보면 하드코딩으로 데이터가 있는 것 같습니다...?

아무리 찾아도 안나와서 상봉으로 검색해보니 script 내부에 theaterJsonData로 되어있네요... 저기서 데이터를 가져와야 할 듯 싶습니다...
데이터는
val url: String =
"http://www.cgv.co.kr/theaters/"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
val doc = conn.get()
val scripts = doc.select("script")
var theatersData: String = ""
for (script in scripts) {
if (script.data().contains("var theaterJsonData")) {
val pattern: Pattern = Pattern.compile(".*var theaterJsonData = ([^;]*);")
val matcher: Matcher = pattern.matcher(script.data())
if (matcher.find()) {
theatersData = matcher.group(1)
break
}
}
}
을 통해서 가져올 수 있습니다.
[{"AreaTheaterDetailList":[{"RegionCode":"01","TheaterCode":"0056","TheaterName":"CGV강남","TheaterName_ENG":null,"IsSelected":true},{"RegionCode":"01","TheaterCode":"0001","TheaterName":"CGV강변","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0229","TheaterName":"CGV건대입구","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0010","TheaterName":"CGV구로","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0063","TheaterName":"CGV대학로","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0252","TheaterName":"CGV동대문","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0230","TheaterName":"CGV등촌","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0009","TheaterName":"CGV명동","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0105","TheaterName":"CGV명동역 씨네라이브러리","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0057","TheaterName":"CGV미아","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0288","TheaterName":"CGV방학","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0030","TheaterName":"CGV불광","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0046","TheaterName":"CGV상봉","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0300","TheaterName":"CGV성신여대입구","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0088","TheaterName":"CGV송파","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0276","TheaterName":"CGV수유","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0150","TheaterName":"CGV신촌아트레온","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0040","TheaterName":"CGV압구정","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0112","TheaterName":"CGV여의도","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0292","TheaterName":"CGV연남","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0059","TheaterName":"CGV영등포","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0074","TheaterName":"CGV왕십리","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0013","TheaterName":"CGV용산아이파크몰","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0131","TheaterName":"CGV중계","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0199","TheaterName":"CGV천호","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0107","TheaterName":"CGV청담씨네시티","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0223","TheaterName":"CGV피카디리1958","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0164","TheaterName":"CGV하계","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"0191","TheaterName":"CGV홍대","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"P001","TheaterName":"CINE de CHEF 압구정","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"01","TheaterCode":"P013","TheaterName":"CINE de CHEF 용산아이파크몰","TheaterName_ENG":null,"IsSelected":false}],"RegionCode":"01","RegionName":"서울","RegionName_ENG":"Seoul","DisplayOrder":"1","IsSelected":true},{"AreaTheaterDetailList":[{"RegionCode":"02","TheaterCode":"0260","TheaterName":"CGV경기광주","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0255","TheaterName":"CGV고양행신","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0257","TheaterName":"CGV광교","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0266","TheaterName":"CGV광교상현","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0348","TheaterName":"CGV광명역","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0232","TheaterName":"CGV구리","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0344","TheaterName":"CGV기흥","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0278","TheaterName":"CGV김포","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0188","TheaterName":"CGV김포운양","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0298","TheaterName":"CGV김포한강","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0351","TheaterName":"CGV다산","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0124","TheaterName":"CGV동백","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0041","TheaterName":"CGV동수원","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0106","TheaterName":"CGV동탄","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0265","TheaterName":"CGV동탄역","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0233","TheaterName":"CGV동탄호수공원","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0226","TheaterName":"CGV배곧","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0155","TheaterName":"CGV범계","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0015","TheaterName":"CGV부천","TheaterName_ENG":null,"IsSelected":false},{"RegionCode":"02","TheaterCode":"0194","TheaterName":"CGV부천역","TheaterName_ENG":null,"IsSelected":false},...]
각각의 지역이 "AreaTheaterDetailList"로 묶여있고 [cgv리스트], 지역명, 지역코드와 같은 방식으로 되어있습니다.
json을 파싱하기 위해 build.gradle에 dependency를 추가해 줍시다.
implementation("org.json:json:20220924")
만약 ClassNotFoundException이 발생하면 서버를 껏다 켜봅시다.
저는 저장시 자동 컴파일 옵션을 사용했더니 저 에러가 나왔습니다.
정확한 구분 방식을 살펴 봅시다.
"AreaTheaterDetailList" :[RegionCode: String, TheaterCode: String, TheaterName: String, TheaterName_ENG: String, IsSelected: boolean], RegionCode: String, RegionName: String, RegionName_ENG: String, DisplayOrder: String, IsSelected: boolean
의 형태로 구성되어 있습니다.
저희가 필요한 데이터는 TheaterCode와 TheaterName과 RegionName만 있으면 됩니다.
강원이 제일 짧으니 강원으로 테스트 해봅시다.
val jsonArray = JSONArray(theatersData)
for (i in 0 until jsonArray.length()) {
val theaters = jsonArray.getJSONObject(i)
println(theaters)
}
{"DisplayOrder":"4","RegionCode":"12","AreaTheaterDetailList":[{"RegionCode":"12","TheaterName":"CGV강릉","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0139"},{"RegionCode":"12","TheaterName":"CGV기린","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0355"},{"RegionCode":"12","TheaterName":"CGV원주","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0144"},{"RegionCode":"12","TheaterName":"CGV원통","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0354"},{"RegionCode":"12","TheaterName":"CGV인제","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0281"},{"RegionCode":"12","TheaterName":"CGV춘천","TheaterName_ENG":null,"IsSelected":false,"TheaterCode":"0070"}],"RegionName":"강원","RegionName_ENG":"Kangwon","IsSelected":false}
각 지역별로 뜯어왔으니 필요한 데이터만 파싱하여 봅시다.
val jsonArray = JSONArray(theatersData)
for (i in 0 until jsonArray.length()) {
val theaters = jsonArray.getJSONObject(i)
val regionName = theaters.getString("RegionName")
val areaTheaterList = theaters.optJSONArray("AreaTheaterDetailList")
for (j in 0 until areaTheaterList.length()) {
val theater = areaTheaterList.getJSONObject(j)
val theaterCode = theater.getString("TheaterCode")
val theaterName = theater.getString("TheaterName")
println("${regionName} ${theaterName} ${theaterCode}")
}
}

영화관 정보도 전부 파싱하여 가져왔습니다. 이제 마지막 날짜 데이터를 구해봅시다.
날짜 데이터는 이전에 우리가 구했던 common/shotimes/iframeTheater.aspx을 사용해 구해와야 할 것 같습니다.
영화관련 데이터는 전부 저 페이지에서 관리하여 기본 페이지에는 들어있지 않습니다...
미리 알았으면 날짜데이터도 가져올 걸 그랬습니다.

날짜는 굉장히 쉽게 구할 것 같습니다. div class="day"로 전부 묶여있어 저걸 가져온 뒤에 href를 그대로 가져다가 사용해도 되고, 날짜만 따로 구해도 될것 같습니다.
저는 우선 href에서 날짜만 따로 빼도록 하겠습니다. 아마 결국엔 href만 가져다가 사용할 것 같습니다..
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=0046&date=20230227"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val days = doc.getElementsByClass("day")
println(days)


3월 10일까지 정상적으로 들어왔으니 파싱만 해줍시다.
이전에 사용했던 Pattern과 Matcher를 사용하여 파싱해봅시다.
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=0046"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val days = doc.getElementsByClass("day")
var date: String = ""
for (day in days) {
val href = day.getElementsByTag("a")[0].attr("href")
val pattern: Pattern = Pattern.compile(".*date=([^&]+).*")
val matcher: Matcher = pattern.matcher(href)
if (matcher.find()) {
date = matcher.group(1)
println(date)
}
}

해당 영화관의 날짜 목록까지 전부 구했습니다.
그럼 합쳐서 한번 진행 해보도록 합시다.
진행 순서는 다음과 같이 될 것 같습니다.
- 영화관 목록을 가져온다. (추후 db에 저장한 뒤 db에서 가져올 예정)
- url에 해당 영화관 코드를 넘겨준 뒤 존재하는 date리스트를 가져옵니다.
- 해당 영화관 코드와 date를 가지고 해당 영화관, 해당 날짜의 시간표를 모두 가져옵니다.
class ShowTimeServiceImpl(
val showTimeRepositorySupport: ShowTimeRepositorySupport
) : ShowTimeService {
val userAgent: String =
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36"
val CGVurl = "http://www.cgv.co.kr"
override fun getShowTime(movieTheater: String): List<ShowTime> {
val theaterlist = getCGVTheaterData()
getCGVShowingTime(theaterlist)
return emptyList()
}
fun getCGVDateList(theatercode: String): ArrayList<String> {
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=${theatercode}"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val days = doc.getElementsByClass("day")
var datelist = ArrayList<String>()
for (day in days) {
val href = day.getElementsByTag("a")[0].attr("href")
val pattern: Pattern = Pattern.compile(".*date=([^&]+).*")
val matcher: Matcher = pattern.matcher(href)
if (matcher.find()) {
datelist.add(matcher.group(1))
}
}
return datelist
}
fun getCGVTheaterData(): ArrayList<Triple<String, String, String>> {
val url: String =
"http://www.cgv.co.kr/theaters/"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
val doc = conn.get()
val scripts = doc.select("script")
var theatersData: String = ""
for (script in scripts) {
if (script.data().contains("var theaterJsonData")) {
val pattern: Pattern = Pattern.compile(".*var theaterJsonData = ([^;]*);")
val matcher: Matcher = pattern.matcher(script.data())
if (matcher.find()) {
theatersData = matcher.group(1)
break
}
}
}
val jsonArray = JSONArray(theatersData)
val theaterlist = ArrayList<Triple<String, String, String>>()
for (i in 0 until jsonArray.length()) {
val theaters = jsonArray.getJSONObject(i)
val regionName = theaters.getString("RegionName")
val areaTheaterList = theaters.optJSONArray("AreaTheaterDetailList")
for (j in 0 until areaTheaterList.length()) {
val theater = areaTheaterList.getJSONObject(j)
val theaterCode = theater.getString("TheaterCode")
val theaterName = theater.getString("TheaterName")
theaterlist.add(Triple(regionName, theaterCode, theaterName))
}
}
return theaterlist
}
fun getCGVShowingTime(theaterlist: ArrayList<Triple<String, String, String>>): Unit {
for (theater in theaterlist) {
val datelist = getCGVDateList(theater.second)
for (date in datelist) {
println("${theater.first} ${theater.third} ${date}")
val url: String =
"http://www.cgv.co.kr/common/showtimes/iframeTheater.aspx?theatercode=${theater.second}&date=${date}"
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.referrer(
"http://www.cgv.co.kr/theaters/"
)
.header(
"Accept",
"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"
)
.header("Accept-Language", "ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7")
.header(
"Cookie",
"ASP.NET_SessionId=test;"
)
val doc = conn.get()
val showtimes = doc.getElementsByClass("col-times")
for (showtime in showtimes) {
val infoMovie = showtime.getElementsByClass("info-movie")[0]
val movieName = infoMovie.getElementsByTag("a")[0].text()
val age = infoMovie.getElementsByTag("i")[0].text()
val categories = infoMovie.getElementsByTag("i")[1].text()
val runningTime = infoMovie.getElementsByTag("i")[2].text()
val comeOut = infoMovie.getElementsByTag("i")[3].text().replace("[^0-9]+".toRegex(), "")
val typeHalls = showtime.getElementsByClass("type-hall")
for (typeHall in typeHalls) {
val infoHall = typeHall.getElementsByClass("info-hall")[0]
val dimension = infoHall.getElementsByTag("li")[0].text()
val whereTheater = infoHall.getElementsByTag("li")[1].text()
val allSeats = infoHall.getElementsByTag("li")[2].text().replace("[^0-9]+".toRegex(), "")
val infoTimeTable = typeHall.getElementsByClass("info-timetable")[0]
val timeinfoes = infoTimeTable.getElementsByTag("li")
for (timeinfo in timeinfoes) {
val datas = timeinfo.getElementsByTag("a")
var starttime = ""
var href = ""
var seatsLeft = ""
if (datas.isEmpty()) {
starttime = timeinfo.getElementsByTag("em")[0].text().replace(":", "")
seatsLeft = "마감"
} else {
starttime = datas[0].attr("data-playstarttime")
href = CGVurl + datas[0].attr("href")
seatsLeft = datas[0].attr("data-seatremaincnt")
}
println("${movieName} ${age}세 ${categories} ${runningTime}분 ${comeOut} ${dimension} ${whereTheater} ${allSeats} ${starttime} ${href} ${seatsLeft}좌석 남음")
}
}
}
}
}
}
fun getShowingTime(movieTheater: String): List<ShowTime> = showTimeRepositorySupport.getShowTime(movieTheater)
}
데이터가 너무 많아 잘려서 마지막에 남아있는 cgv제주노형만 보여드리겠습니다.

광주/전라/제주 CGV제주노형 20230305
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 0900 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=0900&AREA_CD=01&SCREEN_CD=001 168좌석 남음
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 1130 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=1130&AREA_CD=01&SCREEN_CD=001 168좌석 남음
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 1400 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=1400&AREA_CD=01&SCREEN_CD=001 162좌석 남음
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 1630 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=1630&AREA_CD=01&SCREEN_CD=001 168좌석 남음
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 1900 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=1900&AREA_CD=01&SCREEN_CD=001 166좌석 남음
대외비 15세 범죄, 드라마 116분분 20230301 2D 1관 178 2130 http://www.cgv.co.kr/ticket/?MOVIE_CD=20031944&MOVIE_CD_GROUP=20031944&PLAY_YMD=20230305&THEATER_CD=0259&PLAY_START_TM=2130&AREA_CD=01&SCREEN_CD=001 158좌석 남음
분이 이미 붙어있었네요...
만약 영화관 주소가 필요하시다면

해당 영화관 주소로 접속한 뒤(theaterCode를 query로 넘겨줘야 합니다.) theater-info title에 들어있습니다.
'Project > ThreeMovie(영화리뷰및예약도우미)' 카테고리의 다른 글
| 메가박스(MegaBox) 시간표 가져오기(jsoup, kotlin) (0) | 2023.03.02 |
|---|---|
| 롯데시네마(Lotte Cinema) 시간표 가져오기 (jsoup, kotlin) (1) | 2023.03.02 |
| 2023/01/10 회의록 (0) | 2023.01.10 |
| 2023/01/01 회의록 (0) | 2023.01.01 |
| 2022/12/18 회의록 (0) | 2022.12.18 |
댓글