본문 바로가기
Algorithm/WarGame - The Python Challenge

The Python Challenge 4

by 꼬부기가우는소리 2016. 8. 31.
728x90

작성 일자 : 2016. 08. 31

재작성 일자 : 2018. 08. 01


참고 사이트 :

- [docs.python] 20.5. urllib — Open arbitrary resources by URL

- [docs.python] 7.2. re — Regular expression operations


- [docs.python] 22.6. urllib.request — Extensible library for opening URLs

- [점프 투 파이썬] 07-2 정규 표현식 시작하기





python challenge 4단계이다.

사이트에 접속하면 아래와 같은 문구가 나타난다.



현재 html 페이지가 아닌 php 페이지로 이동하라는 의미이다.


HTML

HTML은 하이퍼텍스트 마크업 언어(HyperText Markup Language)라는 의미의 웹 페이지를 위한 지배적인 마크업 언어다. HTML은 제목, 단락, 목록 등과 같은 본문을 위한 구조적 의미를 나타내는 것뿐만 아니라 링크, 인용과 그 밖의 항목으로 구조적 문서를 만들 수 있는 방법을 제공한다. 그리고 이미지와 객체를 내장하고 대화형 양식을 생성하는 데 사용될 수 있다.


PHP

PHP(Hypertext Preprocessor)는 프로그래밍 언어의 일종이다. 원래는 동적 웹 페이지를 만들기 위해 설계되었으며 이를 구현하기 위해 PHP로 작성된 코드를 HTML 소스 문서 안에 넣으면 PHP 처리 기능이 있는 웹 서버에서 해당 코드를 인식하여 작성자가 원하는 웹 페이지를 생성한다. 근래에는 PHP 코드와 HTML을 별도 파일로 분리하여 작성하는 경우가 일반적이며, PHP 또한 웹서버가 아닌 php-fpm(PHP FastCGI Process Manager)을 통해 실행하는 경우가 늘어나고 있다. 또한 PHP는 명령 줄 인터페이스 방식의 자체 인터프리터를 제공하여 이를 통해 범용 프로그래밍 언어로도 사용할 수 있으며 그래픽 애플리케이션을 제작할 수도 있다.


HTML과 PHP의 차이점

HTML은 변하지 않는 정적인 페이지이다. HTML 문장들은 브라우저 요청시 웹서버에서 해당 페이지 문서를 바로 전송해 준다. 반면, PHP는 해석기(parser)에 의해 처리 된 후 웹브라우저로 전달된다. PHP 스크립트는 서버에서 구동되고 웹 페이지가 클라이언트의 브라우저에 전송되기 전에 HTML 코드를 입맛에 맞게 새로 만들거나 수정해서 보낸다.



http://www.pythonchallenge.com/pc/def/linkedlist.php로 들어가면 익숙한 문제 화면이 나타난다.





이번엔 주어지는 힌트가 없다.

F12 (크롬 개발자 도구)를 눌러 소스 코드를 확인해 보도록 하자.





소스코드엔 초록색 주석으로 "urllib may help. DON'T TRY ALL NOTHINGS, since it will never end. 400 times is more than enough." 라 적혀있다.


urllib은 파이썬의 모듈로 웹 상의 문서나 파일을 가져올 수 있고, url, header 등 여러 웹의 정보를 가져올 수 있다.

앞선 문제에서도 해당 urllib의 requests를 사용하여 문제를 해결하였다.


동일한 패턴을 400번 이상 반복할 예정이니 NOTHINGS을 모두 테스트 해보지 말라 경고한다.

NOTHINGS이 무엇인지 아직 모르니 좀 더 힌트를 찾아보도록 한다.




urllib 모듈

[2.7.12 ver] : This module provides a high-level interface for fetching data across the World Wide Web. In particular, the urlopen() function is similar to the built-in function open(), but accepts Universal Resource Locators (URLs) instead of filenames. Some restrictions apply — it can only open URLs for reading, and no seek operations are available.


* Note :

The urllib module has been split into parts and renamed in Python 3 to urllib.request, urllib.parse, and urllib.error. The 2to3 tool will automatically adapt imports when converting your sources to Python 3. Also note that the urllib.request.urlopen() function in Python 3 is equivalent to urllib2.urlopen() and that urllib.urlopen() has been removed.


예시

현재 사용하고 있는 파이썬의 버전은 2.7.11 버전이다.


1
2
3
import urllib
= urllib.urlopen("http://www.pythonchallenge.com/pc/def/linkedlist.php")
print f.read()
cs


urllib.urlopen(url[, data[, proxies[, context]]]) 

Open a network object denoted by a URL for reading.

This supports the following methods: read(), readline(), readlines(), fileno(), close(), info(), getcode() and geturl().


실행 결과는 아래와 같다.



페이지의 소스코드가 그대로 읽혀왔음을 확인할 수 있다.


+ 추가 [3.7.0 ver urllib.requests]

글을 수정하여 현재는 3.7.0 버전에 맞게 재작성되었다.


import urllib.request
html = urllib.request.urlopen("http://www.pythonchallenge.com/pc/def/linkedlist.php")
print(html.read())




소스코드를 더 읽어보면 그림에 링크가 걸려있음을 확인할 수 있다.

<a href="linkedlist.php?nothing=12345"><img src="chainsaw.jpg" border="0"></a>

힌트로 주어진 nothing을 확인할 수 있다.

그림을 클릭해 이동해 보도록 하자.





현재 이동되느 주소는 ?nothing=12345, 그리고 나타난 문구에 따르면 다음 nothing은 44827임을 알 수 있다.

한 번 더 이동해 주도록 한다.





이번에도 다음 nothing 값이 존재하는 것을 확인할 수 있다.

또한 "and the next nothing is "라는 문구가 반복되는 것을 확인할 수 있다.

반복되는 문장에서 숫자만 가져와 url에 대입하여 다음 페이지로 이동하는 파이썬 코드를 작성하도록 해보자.


기존의 코드에서 nothing 값을 가져오는 코드는 아래와 같다.


import urllib.request
import re

html = urllib.request.urlopen("http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing=12345").read().decode()
data = re.findall("nothing is (\d+)", html, re.DOTALL)[-1]
print(data)


nothing is 뒤에 오는 정수값을 통해 다음 페이지로 이동, 그리고 해당 값을 다시 얻어오는 작업을 반복할 것이다.

힌트에서는 해당 작업을 400번 이상 반복한다 하였다.

따라서, 얻은 값을 url에 적용하여 다시 받아오는 방식으로 코드를 변경하면 아래와 같다.


import urllib.request
import re

# 고정 url
url = "http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing="

# 처음 nothing 값
nothing = "12345"

# 다음 nothing 값 확인
# 400번 반복
for i in range(400):
    nextUrl = url + nothing
    html = urllib.request.urlopen(nextUrl).read().decode()
    data = re.findall("nothing is (\d+)", html, re.DOTALL)[-1]
    nothing = data
    print(i, "번째: ", nothing)


동일한 작업을 반복하다 보면 85번째 쯤에 에러가 발생하는 것을 확인할 수 있다.

찾고자 하는 값을 얻을 수 없었기에 data의 index 오류가 발생하였다.





마지막으로 확인한 16044 페이지로 이동하여 확인해본다.





"Yes. Divide by two and keep going."이란 문구를 확인할 수 있다.

16044의 반 값인 8022 값을 입력하여 페이지를 확인하면 다시 새로운 nothing 값이 나타난다.





처음 시작 nothing 값을 25357로 수정하고 동일한 작업을 다시 반복해준다.

이 때, 앞서 발생했던 오류를 방지하기 위해 try-except 문을 사용해준다.


import urllib.request
import re

# 고정 url
url = "http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing="

# 처음 nothing 값
nothing = "25357"

# 다음 nothing 값 확인
# 400번 반복
for i in range(400):
    try:
        nextUrl = url + nothing
        html = urllib.request.urlopen(nextUrl).read().decode()
        data = re.findall("nothing is (\d+)", html, re.DOTALL)[-1]
        nothing = data
        print(i, "번째: ", nothing)
    except:
        # 에러가 발생할 경우, 해당 페이지를 출력 후 종료
        print(html)
        break


코드를 실행하면 163번째에 오류가 발생, 즉 nothing 값을 찾을 수 없고 페이지를 출력 후 종료하는 것을 확인할 수 있다.






이번에는 원하는 다음 페이지 값을 얻었다.

결과값은 peak.html.

즉, "www.pythonchallenge.com/pc/def/peak.html" 가 python challenge 4단계의 정답이 된다.







+ 추가1 (2016. 08. 31 작성)

사실 이 문제를 풀 때 살짝 헤맸었다.

앞서 했던 방식처럼 nothing 값만 찾고 없으면 마지막 소스코드를 출력하는 방식으로 하면 간단한데 일일이 소스코드를 보는 방식으로 하다보니 아래와 같은 문구를 발견하게 되었다.




"There maybe misleading numbers in the text. One example is 82683. Look only for the next nothing and the next nothing is 63579."

문장 그대로 해석해도 다음 nothing에만 신경쓰고 nothing은 63579이니 이동만 하면 되는데.

앞의 misleading만 보고 잘못 한 줄 알고 저 번호 82683 그대로 따라 갔었다.





그리고 당연히 잘못왔다는 문장을 맞이하고 말았다.

조금 생각해보니 해석을 잘못 했다는 것을 깨달았지만.

결과값이 나오기까지 시간이 걸리다 보니 이렇듯 혼동이 일어나기도 하였다.




+ 추가2 (2018. 08. 01 작성)

(참고 : Level4 해설)


from urllib.request import urlopen
import re

uri = "http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing=%s"

num = "12345"
#num = str(16044/2)

pattern = re.compile("and the next nothing is (\d+)")

while True:
content = urlopen(uri % num).read().decode('utf-8')
print(content)
match = pattern.search(content)
if match == None:
break
num = match.group(1)


해설을 확인하면, re 모듈의 findall 함수가 아닌 compile 함수를 사용하였다.

compile 함수는 정규표현식(위 예에서는 ab*)을 컴파일하고 컴파일된 패턴객체(re.compile의 결과로 리턴되는 객체 p)를 이용하여 그 이후의 작업을 수행할 수 있도록 한다.

즉, 앞으로의 작업에서 (\d+)의 위치에 해당하는 값을 가져오도록 패턴을 변수 pattern에 저장시켜 놓았다.


search 함수는 문자열 전체를 검색하여 정규식과 매치되는지 조사한다.

이를 통해 match 객체가 리턴되며 매치되는 값이 존재하지 않으면 None을 리턴한다.

실제로 반환되는 값을 확인해 보면 아래와 같다.


from urllib.request import urlopen
import re

uri = "http://www.pythonchallenge.com/pc/def/linkedlist.php?nothing=%s"
num = "12345"
pattern = re.compile("and the next nothing is (\d+)")
content = urlopen(uri % num).read().decode('utf-8')
match = pattern.search(content)

print(type(match), match)


결과값 :

<class 're.Match'> <re.Match object; span=(0, 29), match='and the next nothing is 44827'>


해당 패턴과 일치하는 값이 존재하기 때문에 반환된 match 값은 "and the next nothing is 44827" 이다.


match 객체의 group 함수는 매치된 문자열을 리턴한다.

단순히 match.group() 을 실행할 경우, 모든 문자열이 반환된다.

하지만 인덱스를 이용하여 match.group(0) 또는 match.group(1) 을 출력할 경우, 각각 "and the next nothing is 44827"와 "44827"이 출력된다.

따라서, nothing 값에 해당하는 match.group(1)을 num에 저장시켜준다.




'Algorithm > WarGame - The Python Challenge' 카테고리의 다른 글

The Python Challenge 5  (0) 2016.09.07
The Python Challenge 3  (0) 2016.08.29
The Python Challange 2  (0) 2016.08.29
The Python Challenge 1  (0) 2016.08.29
The Python Challenge warming up  (0) 2016.08.29

댓글