2023.02.27 - [Project/ThreeMovie(영화리뷰및예약도우미)] - CGV 영화 시간표 크롤링 및 예약 주소 가져오기 (Kotlin, jsoup) 을 참고하고 오시는 걸 추천드립니다.
https://www.lottecinema.co.kr/NLCHS/
롯데시네마
www.lottecinema.co.kr

일단 한번 쿼리를 날려 페이지가 오는지 봅시다.
val url: String =
"https://www.lottecinema.co.kr/NLCHS/"
val conn = Jsoup.connect(url)
val doc = conn.get()
println(doc)

슬프게도 영화관이나 영화 관련 데이터는 전부 api로 관리되는 듯합니다.

테스트 삼아 동성로 사이트로 들어왔습니다.
https://www.lottecinema.co.kr/NLCHS/Cinema/Detail?divisionCode=1&detailDivisionCode=5&cinemaID=5005
divisioncode와 detaildivisioncode, cinemaID로 구분하여 영화관을 찾는 것 같습니다.

상영시간표에서 아무 날짜를 클릭하면 해당 날짜에 영화 상영 시간표를 보내 주는 것 같습니다.
playSeqsHeader의 경우 영화 리스트 같은 느낌이고 해당 정보는 playSeqs에 해당 영화에 더 상세히 저장되어 있는 것 같습니다.
playSeqsHeader는 무시하고 playSeqs를 집중적으로 봅시다.
[
{
"CinemaNameKR": "동성로",
"CinemaNameUS": "DongSeong-Ro",
"MovieNameKR": "앤트맨과 와스프: 퀀텀매니아",
"MovieNameUS": "Ant-Man and the Wasp: Quantumania",
"ViewGradeCode": 12,
"ViewGradeNameKR": "12",
"ViewGradeNameUS": "12",
"FilmCode": 200,
"FilmNameKR": "2D",
"FilmNameUS": "2D",
"SoundTypeCode": 100,
"SoundTypeNameKR": "일반사운드",
"SoundTypeNameUS": "(EN)일반사운드",
"FourDTypeCode": 100,
"FourDTypeNameKR": null,
"FourDTypeNameUS": null,
"TranslationDivisionCode": 900,
"TranslationDivisionNameKR": null,
"TranslationDivisionNameUS": null,
"AccompanyTypeCode": 10,
"AccompanyTypeNameKR": "일반",
"AccompanyTypeNameUS": "US_일반",
"AccompanyTypeShortName": null,
"ScreenDivisionCode": 300,
"ScreenDivisionNameKR": "샤롯데",
"ScreenDivisionNameUS": "CharLotte",
"IsBookingYN": "Y",
...
"ScreenDesc": "",
"StrtTimeCompare": null,
"PosterURL": "http://caching.lottecinema.co.kr//Media/MovieFile/MovieImg/202302/19562_103_1.jpg",
"ScreenFloor": "5",
...
"CinemaID": 5005,
"RepresentationMovieCode": "19562",
"MovieCode": "19562",
"PlayDt": "2023-02-28",
"ScreenID": 501,
"PlaySequence": 5,
"StartTime": "20:55",
"EndTime": "23:09",
"TotalSeatCount": 36,
"BookingSeatCount": 28,
"SequenceNoGroupCode": 1,
"SequenceNoGroupNameKR": "일반",
"SequenceNoGroupNameUS": "(EN)일반",
"ScreenNameKR": "샤롯데",
"ScreenNameUS": "CINEMA 1",
"WeekendDivisionCode": 200
},
{
...
}
]
저희는 MovieNameKR, FilmNameKR, ScreenDivisionNameKR 정도만 필요해 보이지만 바로 예약 정보로 이동하기 위해서 어떤 데이터가 필요한지 모르므로 보류합시다. 개봉날짜는 영화 시간표 페이지에 없는 것 같습니다. 영화 상세정보 페이지에 있을 테지만, 저희는 다음 영화에서 상세정보를 따로 가져오기에 그 부분은 빼고 하겠습니다.
한번 예약 버튼을 눌러봅시다.

api를 3번 더 호출하는 것 같습니다. 바로 예약 사이트로 이동이 가능할지 모르겠습니다...
일단 api payload를 뜯어봅시다. 3단계로 구성되어 진행되는 것 같습니다.
프리셋 예매를 눌러봅시다.

https://www.lottecinema.co.kr/NLCHS/Ticketing?movieCd=19551&movieName=%EB%8C%80%EC%99%B8%EB%B9%84&screenCd=1|4|6004&screenName=%EC%88%98%EC%99%84(%EC%95%84%EC%9A%B8%EB%A0%9B)&releaseDate=2023-03-02
저기 있는 값 중 하나라도 빠지면 똑바로 인식이 안 되는 것 같습니다.
쿠키를 뜯어보니 최근에 확인했던 영화와 영화관, 시간을 저장해 놓고 쿠키 값을 가져와서 사용하는 것 같습니다.
영화관의 영화 시간표를 먼저 가져와 봅시다.


https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx 에 post로 요청을 보냅니다.
Param은 GetPlaySequence 함수에 playdate, cinemaID, osVesion이 핵심으로 보입니다. 혹시 모르니까 하나씩 빼면서 테스트해 봅시다.
하나라도 빠지면
<body>
{"IsOK":"false","ResultMessage":"호출 파라미터가 부족합니다.","ResultCode":null,"EventResultYn":null}
</body>
이런 에러메시지를 리턴합니다...
전부 다 넣어서 호출해 보겠습니다.
val url: String =
"https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx"
var paramlist = HashMap<String, String>()
paramlist.put("MethodName", "GetPlaySequence")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
paramlist.put("playDate", "2023-03-03")
paramlist.put("cinemaID", "1|3|4008")
paramlist.put("representationMovieCode", "")
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body()
println(JSONObject(paramlist).toString())
println(doc)

SequenceNoGroupNameKR : "조조"
SequenceNoGroupNameUS : "(EN)조조"
영어 데이터도 같이 저장을 하려 했는데 가끔 이상한 친구들이 있네요... 롯데시네마도 저런 건 어쩔 수 없나 봅니다.
단순히 toString을 하면 json형식으로 보내지지 않아 캐스팅을 한 뒤에 보내주셔야 합니다.
그럼 이제 들어온 데이터를 파싱 해봅시다.
대전센트럴 3월 3일 자입니다.

val url: String =
"https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx"
var paramlist = HashMap<String, String>()
paramlist.put("MethodName", "GetPlaySequence")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
paramlist.put("playDate", "2023-03-03")
paramlist.put("cinemaID", "1|3|4008")
paramlist.put("representationMovieCode", "")
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc)
val playSeqs = data.getJSONObject("PlaySeqs").getJSONArray("Items")
for (i in 0 until playSeqs.length()) {
val playdata = playSeqs.getJSONObject(i)
println(playdata)
val movieNameKR = playdata.get("MovieNameKR")
val movieNameUS = playdata.get("MovieNameUS")
val screenNameKR = playdata.get("ScreenNameKR")
val screenNameUS = playdata.get("ScreenNameUS")
val startTime = playdata.get("StartTime")
val endTime = playdata.get("EndTime")
val totalSeatCount = playdata.get("TotalSeatCount")
val playSequence = playdata.get("PlaySequence")
val bookingSeatCount = playdata.get("BookingSeatCount")
println("${movieNameKR} ${movieNameUS}\n${screenNameKR} ${screenNameUS}\n${startTime} ${endTime}\n${totalSeatCount} ${bookingSeatCount} ${playSequence}")
}

이제 영화관 데이터들을 가져와 봅시다.
아무리 찾아봐도 관련 데이터가 없습니다...


아마 header_section을 만드는 쪽에서 복호화가 되어있는 것 같습니다.
어쩔 수 없이 모바일 페이지를 뒤져봅시다.



해당 주소로 메서드 GetCinemaItems 요청을 보내서 받아오면 될 것 같습니다. osType으로 구분을 하나 봅니다.
osType이나 channelType은 아마 요청을 보낸 환경인 것 같으니 이전에 사용한 H0와 W로 해서 보내봅시다.
val url: String =
"https://www.lottecinema.co.kr/LCWS/Cinema/CinemaData.aspx"
var paramlist = HashMap<String, String>()
paramlist.put("MethodName", "GetCinemaItems")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc)
println(data)
{"IsOK":"true","EventResultYn":null,"ResultMessage":"SUCCESS","Cinemas":{"ItemCount":0,"Items":[{"CinemaNameUS":"Gasan","CinemaName":"가산디지털","StageGreetingYN":"N","CinemaAreaCode":null,"CinemaAddrSummary":"디지털로10길 9 (현대아울렛 가산)","SmartOrderYN":"Y","Latitude":"37.4775952","CinemaAreaSort":0,"DivisionCode":1,"Longitude":"126.8890717","OpenDtYN":"N","Active":0,"CinemaID":1013,"CinemaNameKR":"가산디지털","DetailDivisionNameKR":null,"CinemaOprtDivCd":null,"SortSequence":1,"CinemaAreaName":null,"DetailDivisionCode":"0001","Distance":0},...}
다행히 성공적으로 넘어옵니다.
Cinemas의 Items만 받아와서 파싱 해주면 될 것 같습니다.
영화관 영화 데이터를 가져올 때 필요했던 파라미터는 divisionCode, detailDivisionCode, cinemaID가 있었습니다.
val url: String =
"https://www.lottecinema.co.kr/LCWS/Cinema/CinemaData.aspx"
var paramlist = HashMap<String, String>()
paramlist.put("MethodName", "GetCinemaItems")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc)
val cinemas = data.getJSONObject("Cinemas").getJSONArray("Items")
for (i in 0 until cinemas.length()) {
val cinema = cinemas.getJSONObject(i)
val cinemaNameKR = "롯데시네마 " + cinema.getString("CinemaName") + "점"
val cinemaNameEN = "LotteCinema " + cinema.getString("CinemaNameUS")
val divisionCode = cinema.get("DivisionCode")
val detailDivisionCode = cinema.get("DetailDivisionCode")
val cinemaID = cinema.get("CinemaID")
println("${cinemaNameEN} ${cinemaNameKR} ${divisionCode} ${detailDivisionCode} ${cinemaID}")
}


DetailDivisionCode가 다릅니다...?


저희가 구한 데이터로 입력을 해도 다행히 성공적으로 들어옵니다.
날짜 데이터는 2가지 api에서 받아옵니다.




GetMoviePlayDates는 상영이 있음과 상관없이 전체 날짜인 것 같습니다.
3월 13일(현재 3월 1일) 날짜의 시간표는 회색이며 누르면 조회 가능한 상영시간이 없다고 합니다.


이곳에 Cinemas도 있네요...?

복호화하기 전에 가져다가 사용한 것이거나 code 구분을 위해 사용하는 것 같습니다...
저희는 PlayDate에서 파싱 하여 날짜만 가져오면 될 것 같습니다.
val theaterlist = ArrayList<HashMap<String, Any>>()
val url: String =
"https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx"
var paramlist = HashMap<String, Any>()
paramlist.put("MethodName", "GetInvisibleMoviePlayInfo")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
paramlist.put("cinemaList", "1|0986|9071")
paramlist.put("movieCd", "")
paramlist.put("playDt", "2023-03-03")
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc).getJSONObject("PlayDates").getJSONArray("Items")
for (i in 0 until data.length()) {
val playdate = data.getJSONObject(i).getString("PlayDate").split(" ")
println(playdate[0])
}


데이터는 전부 수집한 거 같으니 합쳐봅시다.
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"
override fun getShowTime(movieTheater: String): List<ShowTime> {
val theaterlist = getLotteCinemaTheaterData()
getLotteCinemaShowingTime(theaterlist)
return emptyList()
}
fun getLotteCinemaTheaterData(): ArrayList<HashMap<String, Any>> {
val theaterlist = ArrayList<HashMap<String, Any>>()
val url: String =
"https://www.lottecinema.co.kr/LCWS/Cinema/CinemaData.aspx"
var paramlist = HashMap<String, Any>()
paramlist.put("MethodName", "GetCinemaItems")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc)
val cinemas = data.getJSONObject("Cinemas").getJSONArray("Items")
for (i in 0 until cinemas.length()) {
val cinema = cinemas.getJSONObject(i)
val cinemaNameKR = "롯데시네마 " + cinema.getString("CinemaName") + "점"
val cinemaNameEN = "LotteCinema " + cinema.getString("CinemaNameUS")
val divisionCode = cinema.get("DivisionCode")
val detailDivisionCode = cinema.get("DetailDivisionCode")
val cinemaID = cinema.get("CinemaID")
val theatermap = HashMap<String, Any>()
theatermap.put("cinemaNameEN", cinemaNameEN)
theatermap.put("cinemaNameKR", cinemaNameKR)
theatermap.put("divisionCode", divisionCode)
theatermap.put("detailDivisionCode", detailDivisionCode)
theatermap.put("cinemaID", cinemaID)
theaterlist.add(theatermap)
}
return theaterlist
}
fun getLotteCinemaDateList(theatercode: String): ArrayList<String> {
var datelist = ArrayList<String>()
val url: String =
"https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx"
var paramlist = HashMap<String, Any>()
paramlist.put("MethodName", "GetInvisibleMoviePlayInfo")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
paramlist.put("cinemaList", theatercode)
paramlist.put("movieCd", "")
paramlist.put("playDt", "2023-03-03")
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc).getJSONObject("PlayDates").getJSONArray("Items")
for (i in 0 until data.length()) {
val playdate = data.getJSONObject(i).getString("PlayDate").split(" ")
datelist.add(playdate[0])
}
return datelist
}
fun getLotteCinemaShowingTime(theaterlist: ArrayList<HashMap<String, Any>>): Unit {
for (theater in theaterlist) {
val cinemaCode: String =
"${theater.get("divisionCode")}|${theater.get("detailDivisionCode")}|${theater.get("cinemaID")}"
val datelist = getLotteCinemaDateList(cinemaCode)
for (date in datelist) {
val url: String =
"https://www.lottecinema.co.kr/LCWS/Ticketing/TicketingData.aspx"
var paramlist = HashMap<String, String>()
paramlist.put("MethodName", "GetPlaySequence")
paramlist.put("channelType", "HO")
paramlist.put("osType", "W")
paramlist.put("osVersion", userAgent)
paramlist.put("playDate", date)
paramlist.put("cinemaID", cinemaCode)
paramlist.put("representationMovieCode", "")
val conn = Jsoup.connect(url)
.userAgent(userAgent)
.data("ParamList", JSONObject(paramlist).toString())
val doc = conn.post().body().text()
val data = JSONObject(doc)
val playSeqs = data.getJSONObject("PlaySeqs").getJSONArray("Items")
println("${theater.get("cinemaNameKR")} ${theater.get("cinemaNameEN")} ${date}")
for (i in 0 until playSeqs.length()) {
val playdata = playSeqs.getJSONObject(i)
val movieNameKR = playdata.get("MovieNameKR")
val movieNameUS = playdata.get("MovieNameUS")
val screenNameKR = playdata.get("ScreenNameKR")
val screenNameUS = playdata.get("ScreenNameUS")
val startTime = playdata.get("StartTime")
val endTime = playdata.get("EndTime")
val totalSeatCount = playdata.get("TotalSeatCount")
val playSequence = playdata.get("PlaySequence")
val bookingSeatCount = playdata.get("BookingSeatCount")
println("${movieNameKR} ${movieNameUS}\n${screenNameKR} ${screenNameUS}\n${startTime} ${endTime}\n${totalSeatCount} ${bookingSeatCount} ${playSequence}")
}
}
}
}
}

롯데시네마 제주삼화지구점 LotteCinema Jeju Samhwa 2023-03-04
대외비 The Devil's Deal
4관 CINEMA 4
09:30 11:35
56 56 1
대외비 The Devil's Deal
4관 CINEMA 4
11:50 13:55
56 56 2
대외비 The Devil's Deal
4관 CINEMA 4
14:10 16:15
56 56 3
대외비 The Devil's Deal
4관 CINEMA 4
16:30 18:35
56 56 4
대외비 The Devil's Deal
4관 CINEMA 4
18:50 20:55
56 56 5
대외비 The Devil's Deal
4관 CINEMA 4
21:10 23:15
56 56 6
데이터가 성공적으로 들어왔습니다.
'Project > ThreeMovie(영화리뷰및예약도우미)' 카테고리의 다른 글
| 2023/03/02 회의록 (0) | 2023.03.02 |
|---|---|
| 메가박스(MegaBox) 시간표 가져오기(jsoup, kotlin) (0) | 2023.03.02 |
| CGV 영화 시간표 크롤링 및 예약 주소 가져오기 (Kotlin, jsoup) (0) | 2023.02.27 |
| 2023/01/10 회의록 (0) | 2023.01.10 |
| 2023/01/01 회의록 (0) | 2023.01.01 |
댓글