본문 바로가기
Project/ThreeMovie(영화리뷰및예약도우미)

롯데시네마(Lotte Cinema) 시간표 가져오기 (jsoup, kotlin)

by HDobby 2023. 3. 2.

 

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

데이터가 성공적으로 들어왔습니다.

728x90

댓글