'기본 카테고리' 카테고리의 다른 글
2008.03.14 게임기획전문가필기 결과 (0) | 2008.03.14 |
---|---|
대리, 과장, 부장 등의 영어 표현은? (0) | 2008.03.07 |
게임기획전문가 자격증 실기 - 기획서 작성의 대략 방식 (0) | 2008.03.02 |
게임기획전문가 실기 후기 및 정보 (2) | 2008.03.02 |
Recover MySQL root password (0) | 2008.02.28 |
2008.03.14 게임기획전문가필기 결과 (0) | 2008.03.14 |
---|---|
대리, 과장, 부장 등의 영어 표현은? (0) | 2008.03.07 |
게임기획전문가 자격증 실기 - 기획서 작성의 대략 방식 (0) | 2008.03.02 |
게임기획전문가 실기 후기 및 정보 (2) | 2008.03.02 |
Recover MySQL root password (0) | 2008.02.28 |
게임기획전문가 자격증 실기 - 기획서 작성의 대략 방식 [게임기획] |
| |||
| ||||
게임기획전문가 자격증 실기 - 기획서 작성의 대랙 방식을 설명해 드립니다. 내용자체의 양식학습도 힘들고 파트별 개념잡기도 쉽지 않기에 전문기관을 두드리거나 전문가에게 개인지도를 받는 것이 가장 좋으며 현실적인 취업부분도 가능합니다. 대부분 기획자들이 게임 기획서를 쓰는 데 있어서 어떤 작업 경로를 통해서 무슨 내용들을 써야하는 지 감을 잡기 어려울 때가 많습니다. 특히 정확한 작업 플로우를 거치지 않고, 무작위로 작업을 하다가는 기획 도중 혼란에 빠지 거나 자신이 쓰고자 하는 의도의 기획서가 나오지 않을 수도 있습니다. 아래의 내용은 그 동안 제가 기획서 쓰면서 나름대로 쌓였던 노하우를 정리한 것으로 참조가 되었으면 합니다. 혼자서 진행하는 경우도 있지만, 사내의 마케팅 팀과 더불어 일을 추진할 수도 있지요. 개발 전 준비가 필요한 것은 다음과 같은 것들이 있습니다. 1.게임 장르 물론 위와 같은 거 외에도 게임 보급 전략 및 게임 규모등과 같은 다른 요소들도 있지만, 그 작업으로는 다음과 같은 것들이 있습니다. 1.게임 스토리 합니다. 리소스 정리 역시 생각나는 데로 정리하는 것이 아니라, 기준을 세워서 해야 하는데요. 아래의 기준은 제 나름대로 세웠던 기준입니다. 1.케릭터 리소스 2.오브젝트 리소스 아이템이면 아이템 설정을 잡아주는 것이지요. 하지만, 오브젝트를 실마리로 하여서 기획안의 초입을 들어가는 경우는 거의 아직까지는 보지 못했습니다. 3.시스템 리소스 많은 rpg나 시뮬레이션 중심으로 이러한 방식이 사용되고 있습니다. 하지만 시스템 리소스는 단독으로 언급될 수 있는 것은 아니고 월드 리소스와 케릭터 리소스 오브젝트 리소스 등과 같이 혼합되어서 언급되는 경우가 많기 때문에 이것 역시 시스템 단독으로 리소스 정리를 들어갈 수는 없겠지요.말이 너무 어려운 것 같아서 예를 들어서 설명을 드리겠습니다. 시스템적으로 정치 체제를 넣는데 이러한 정치는 귀족들의 선거에 의해서 선출이 된다는 룰을 정했 다면, 그 선거를 하는 구역을 어떻게 나눌 것인가에 대한 지형적인 설정을 월드 리소스에서 해주어야하며, 케릭터 파라미터로 계급과 직업의 파라미터가 필요하다는 설정도 잡아야겠지요. 다음으로 투표소와 같은 오브젝트 세팅도 필요하겠지요. 4.월드 리소스 으로 이것들을 먼저 설정을 해놓는 작업으로 시작하여서 기획서를 시작하는 경우가 많습니다. 5.스토리 리소스 게임을 만드는 것은 불가능하겠지요. 아마도 게임이 가지는 양방향성 면에서 현저하게 떨어지니까요. 하지만 페키지 게임을 만드는 데 탄탄한 스토리를 중심으로 구현한다면 좋은 작품을 만들수 있겠지요. 그 예로는 코지마 히데오의 메탈기어 솔리드와 같은 게임을 들 수가 있습니다. |
대리, 과장, 부장 등의 영어 표현은? (0) | 2008.03.07 |
---|---|
성공하는 사람들은 뭔가 다르다! 계획에서 실천까지 관리 노하우 (0) | 2008.03.06 |
게임기획전문가 실기 후기 및 정보 (2) | 2008.03.02 |
Recover MySQL root password (0) | 2008.02.28 |
Beautiful Soup (0) | 2008.02.26 |
방금 게임기획전문가 실기시험 보고 왔습니다.(2007년 10월 6일) 혼자 준비하면서 이 넓은 인터넷의 바다에서도 제대로 된 정보를 찾은 적이 없어서 이렇게 후기를 올립니다. 올해는 끝났으니 내년에 준비하시는 기획자 및 지망생 여러분들에게 도움이 되었으면 합니다.
●장소 : 저는 뚝섬유원지역 부근 산업인력공단 동부지사에서 시험쳤습니다.
●시험시간 : 8시 30분까지 입실이라고 수험표에 적혀있습니다. 저는 10분 지각했는데 저보다 10분 늦게 오는 사람도 있더군요. 9시까지는 봐주는 것 같으니까 혹시 늦잠 자더라도 포기하지 말고 달려가서 시험보고 오세요. 그리고 9시 5분에 시작했는데 저는 늦게 푼 편이 아니었는데 1시 정도에 스케치까지 끝냈습니다. 게임학원 같은 곳에서 단체로 온 몇 분은 빨리 나가시더군요. 학원에서 자격증에 대한 공부도 따로 하나 봅니다.
●시험방식 : 14문제가 나오는데 그것을 워드로 작성해서 출력하고 출력물 위의 지정한 공백에 스케치 하고나서 디스켓과 함께 제출하고 나오면 됩니다. 스캔 뜨고 이런 복잡한 과정 없습니다. 편하게 가서 보세요. 그리고 저는 다 풀고 출력하니까 5장 나왔습니다. 많이 하신 분들은 7장도 한 것 같더군요. 컨닝은 아니고 슬쩍 둘러봤지요.
●지참물 : 산업인력공단 사이트에는 지참물로 싸인펜, 스케치용 연필, 스카치 테이프 등을 제시하고 있는데, 다 필요없습니다. 시험이 워드로 진행되기 때문에 스케치용 연필 밖에 쓸 일이 없더군요. A4용지로 출력한 다음 자신이 설정한 공백에 스케치를 해야 하기 때문에 저는 그냥 샤프로 했습니다.
●시험문제 : 아마 이게 제일 궁금하겠지요?
1번 : Game과 Story의 차이
2번 : Game과 Simulation의 차이
3번 : Game과 Puzzle의 차이
4번 : Game과 Toy의 차이
해설 -> 게임 개론서나 라프코스터의 재미이론 같은 책들 찾아보세요. 금방 나옵니다.
5번 : 다음의 가상의 어떤 국가의 상황이다. 이 여건에 맞추어 기획한 MMORPG에 대한 마케팅 전략에 대해 기술하라.
1) 비디오게임기 보급현황 : 전 가전의 90% 이상 보급되어있다.
2) 정보통신 인프라 : 초고속 인터넷이 전 가정에 보급되어 있다. 하지만 다운로드에 대해 부정적으로 인식하고 있는 사람들이 많다.
3) 소비자 구매성향 : 게임 CD 판매율이 높아지고 있다.
4) 아이템 매매에 대한 소비자의 인식 : 아이템을 구입하고 타인에게 거래하는 것에 대해 매우 부정적으로 인식하고 있다.
해설 -> 마케팅에 대해 전문적으로 배우지 않았어도 유추는 하실 수 있겠죠? 비디오게임 보급률과 구매성향, 다운로드에 대한 부정적 인식을 보았을 때 당연히 콘솔용 CD로 출시되어야 하고 콘솔용이니까 싱글과 멀티 두 가지 플레이가 다 가능해야 하고 MMORPG니까 CD는 싸게 판매하고 멀티플레이 이용자들에게는 저렴한 정액요금을 적용해야겠지요? 아이템에 대한 인식이 부정적이니까 부분 유료화는 별로구요. 물론 정답 아닙니다. 다른 답도 있으면 답글 좀 부탁합니다.
6번~9번 : 가장 시간을 많이 잡아먹는 혼합형 문제다. '로미오와 줄리엣'의 스토리를 게임 시나리오로 준 다음 밑의 문제들을 해결하게 한다.
6번 : 두 명의 주인공 캐릭터를 스케치하고 컨셉에 대해 디자이너에게 설명하라.
해설 -> 머... 답이 있나요. 자신이 상상한 로미오와 줄리엣 그리고 컨셉 설명해야죠. 전 그냥 원본 로미오와 줄리엣에 맞춰서 했어요. 디카프리오 영화처럼 퓨전하게 할까 하다가 스케치 할 자신이 없어서요.
7번 : 줄리엣의 방이 보이는 곳을 중심으로 줄리엣의 집을 스케치하고 배경 컨셉에 대해 디자이너에게 설명하라.
해설 -> 이것도 답 없죠. 저는 너무 방을 중심으로 스케치한 듯 하네요. 거의 빵점이지 않을까. 집의 형태와 정원을 좀더 자세하게 그렸어야 하는데... 아쉬움이 남는 항목이네요.
8번 : 디볼트와 로미오의 격투장면을 철권과 같은 대전형 게임으로 만들 경우 인터페이스에 대해 스케치하고 구성 요소들에 대해 설명하라.
해설 -> 대전형 게임 인터페이스 그린 다음 부분 부분에 대해 설명했어요.
9번 : 로렌스 신부는 로미오에게 줄리엣과 모의한 내용의 편지를 전달해야 하는데 착오가 생겨 실패한다. 이것을 모티브로 니드 포 스피드와 같은 레이싱 게임을 만들려고 한다. (제약 - 정해진 시간 안에 해야한다.)
1) 게임의 목표를 설정하라.
해설 -> 주어진 제약을 뛰어넘어 로미오에게 편지를 전달하는 것이겠지요.
2) 장애요소 3가지를 설정하고 그것의 역활에 대해 기술하라.
해설 -> 제약이 시간이니 이것은 상수고, 그러면 속도와 거리가 변수가 될 수 있겠네요. 거리도 상수로 지정할 수 있겠지만 그러면 변수가 하나여서 너무 재미가 없겠죠. 장애요소는 커브길, 마차, 새총 세 가지로 설정했습니다. 셋 중에 커브길은 거리, 마차와 새총은 속도에 영향을 주고록 설정했습니다. 그리고 난이도에 따라 그 출현량은 변동한다고 추가설명 했습니다.
3) 시작점, A지역, B지역, C지역, 도착점의 다섯 부분으로 나누어 난이도의 레벨을 설정하라. 단, 난이도가 최대가 되는 지점이 하나는 있어야 하며 근거있게 수행하라.
해설 -> 이건 그래프를 워드프로세서 안에서 만들어야 하는데요. 일반적인 소설의 플롯에 대한 이론에 맞춰서 하면 됩니다. 시작점을 발단으로 잡고 C지역을 절정으로 해서 난이도를 최대로 하고 도착점을 결말로 하면 될 것 같습니다.
10번 : 다음 게임의 장르를 적고 게임의 특징과 재미요소에 대해 논하라.
1) 스타크래프트
2) 철권
3) 팰콘4.0
해설 ->이 정도 게임은 알아서 설명하실 줄 알아야 기획자 합니다. 그래서 저는 아직 기획자가 못되나 봅니다. ㅠㅠ. 철권은 2D인지 3D인지 기억이 안나서 슬쩍 그 부분은 빼고 설명했구요. 팰콘은 그 쪽 장르의 일반적인 특징에 대해 간략히 적었고 추측한 내용을 슬그머니 추가했습니다. 아무래도 버전이 4.0이니 기능이 많이 좋겠다 싶어서요. 스타크는 머 워낙 많이 해 본 게임이라.
11~14번 : 다음 주어진 성향을 가진 MMORPG 유저들에 맞는 서비스에 대해 기획자의 관점에서 논하라.
11번 : Killer
12번 : Achiever
13번 : Socializer
14번 : Explorer
해설 -> 제우미디어에서 나온 '온라인 게임기획, 이렇게 한다'라는 책에 보면 나와있습니다.
문제 어떤가요? 어렵나요? 6~9번의 것을 제외하고는 평소에 게임에 관심있고 게임기획 개론서 독서를 몇 번만 했다면 작성은 할 수 있을 것입니다. 그리고 6~9번의 것은 간단한 게임이라도 몇 번만 기획해 보았다면 작성할 수 있을 것입니다. 실기는 일주일 준비한다고 되는 시험이 아니었습니다. 평소에 관심있는만큼 성적이 나올것 같네요. 아참... 기획자 준비하시는 분들... 제발 준비하지 마세요. 경쟁률 높아집니다. 아직 취업도 못했는데... ㅎㅎ
성공하는 사람들은 뭔가 다르다! 계획에서 실천까지 관리 노하우 (0) | 2008.03.06 |
---|---|
게임기획전문가 자격증 실기 - 기획서 작성의 대략 방식 (0) | 2008.03.02 |
Recover MySQL root password (0) | 2008.02.28 |
Beautiful Soup (0) | 2008.02.26 |
Python and HTML Processing (0) | 2008.02.26 |
You can recover MySQL database server password with following five easy steps.
Step # 1: Stop the MySQL server process.
Step # 2: Start the MySQL (mysqld) server/daemon process with the –skip-grant-tables option so that it will not prompt for password
Step # 3: Connect to mysql server as the root user
Step # 4: Setup new root password
Step # 5: Exit and restart MySQL server
Here are commands you need to type for each step (login as the root user):
# /etc/init.d/mysql stop
Output:
Stopping MySQL database server: mysqld.
# mysqld_safe --skip-grant-tables &
Output:
[1] 5988Starting mysqld daemon with databases from /var/lib/mysqlmysqld_safe[6025]: started
# mysql -u root
Output:
Welcome to the MySQL monitor. Commands end with ; or \g.Your MySQL connection id is 1 to server version: 4.1.15-Debian_1-logType 'help;' or '\h' for help. Type '\c' to clear the buffer.mysql>
mysql> use mysql;
mysql> update user set password=PASSWORD("NEW-ROOT-PASSWORD") where User='root';
mysql> flush privileges;
mysql> quit
# /etc/init.d/mysql stop
Output:
Stopping MySQL database server: mysqldSTOPPING server from pid file /var/run/mysqld/mysqld.pidmysqld_safe[6186]: ended[1]+ Done mysqld_safe --skip-grant-tables
# /etc/init.d/mysql start
# mysql -u root -p
Want to stay up to date with the latest Linux tips, news and announcements? Subscribe to our free e-mail newsletter or full RSS feed to get all updates. You can Email this page to a friend.
게임기획전문가 자격증 실기 - 기획서 작성의 대략 방식 (0) | 2008.03.02 |
---|---|
게임기획전문가 실기 후기 및 정보 (2) | 2008.03.02 |
Beautiful Soup (0) | 2008.02.26 |
Python and HTML Processing (0) | 2008.02.26 |
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글) (0) | 2008.02.16 |
You didn't write that awful page. You're just trying to get some data out of it. Right now, you don't really care what HTML is supposed to look like.
Neither does this parser.
"A tremendous boon." -- Python411 Podcast
[ Download | Documentation | What's New | Contributors | To-do list | Forum ] If Beautiful Soup has saved you a lot of time and money, please share the wealth.Beautiful Soup is a Python HTML/XML parser designed for quick turnaround projects like screen-scraping. Three features make it powerful:
Beautiful Soup parses anything you give it, and does the tree traversal stuff for you. You can tell it "Find all the links", or "Find all the links of class externalLink", or "Find all the links whose urls match "foo.com", or "Find the table heading that's got bold text, then give me that text."
Valuable data that was once locked up in poorly-designed websites is now within your reach. Projects that would have taken hours take only minutes with Beautiful Soup. Download Beautiful Soup
The latest version is Beautiful Soup version 3.0.5, released December 12, 2007. You can download it as a single, self-contained file, or as a tarball with installer script and unit tests. Beautiful Soup is licensed under the same terms as Python itself, so you can drop it into almost any Python application (or into your library path) and start using it immediately.
Beautiful Soup works with Python versions 2.3 and up. It works best with Python versions 2.4 and up. If you don't have Python 2.4, you should install the cjkcodecs
, iconvcodec
, and chardet
libraries. If you don't do this, Beautiful Soup will still work, but it won't be very good at parsing documents in Asian encodings.
Older versions are still available: the 1.x series works with Python 1.5, and the 2.x series has a fairly large installed base.
This document (source) is part of Crummy, the webspace of Leonard Richardson (contact information). It was last modified on Friday, December 21 2007, 18:57:10 Nowhere Standard Time and last built on Monday, February 25 2008, 23:00:01 Nowhere Standard Time.
| Document tree: Site Search: |
게임기획전문가 실기 후기 및 정보 (2) | 2008.03.02 |
---|---|
Recover MySQL root password (0) | 2008.02.28 |
Python and HTML Processing (0) | 2008.02.26 |
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글) (0) | 2008.02.16 |
[팁] vi editor와 관련된 유용한 팀[펌] (0) | 2008.02.14 |
Home | People | HTML | Emulation |
---|---|---|---|
Python |
Various Web surfing tasks that I regularly perform could be made much easier, and less tedious, if I could only use Python to fetch the HTML pages and to process them, yielding the information I really need. In this document I attempt to describe HTML processing in Python using readily available tools and libraries.
NOTE: This document is not quite finished. I aim to include sections on using mxTidy to deal with broken HTML as well as some tips on cleaning up text retrieved from HTML resources.
Depending on the methods you wish to follow in this tutorial, you need the following things:
Accessing sites, downloading content, and processing such content, either to extract useful information for archiving or to use such content to navigate further into the site, require combinations of the following activities. Some activities can be chosen according to preference: whether the SGML parser or the XML parser (or parsing framework) is used depends on which style of programming seems nicer to a given developer (although one parser may seem to work better in some situations). However, technical restrictions usually dictate whether certain libraries are to be used instead of others: when handling HTTP redirects, it appears that certain Python modules are easier to use, or even more suited to handling such situations.
Fetching standard Web pages over HTTP is very easy with Python:
import urllib
# Get a file-like object for the Python Web site's home page.
f = urllib.urlopen("http://www.python.org")
# Read from the object, storing the page's contents in 's'.
s = f.read()
f.close()
Sometimes, it is necessary to pass information to the Web server, such as information which would come from an HTML form. Of course, you need to know which fields are available in a form, but assuming that you already know this, you can supply such data in the urlopen
function call:
# Search the Vaults of Parnassus for "XMLForms".
# First, encode the data.
data = urllib.urlencode({"find" : "XMLForms", "findtype" : "t"})
# Now get that file-like object again, remembering to mention the data.
f = urllib.urlopen("http://www.vex.net/parnassus/apyllo.py", data)
# Read the results back.
s = f.read()
s.close()
The above example passed data to the server as an HTTPPOST
request. Fortunately, the Vaults of Parnassus is happy about such requests, but this is not always the case with Web services. We can instead choose to use a different kind of request, however:
# We have the encoded data. Now get the file-like object...
f = urllib.urlopen("http://www.vex.net/parnassus/apyllo.py?" + data)
# And the rest...
The only difference is the use of a?
(question mark) character and the adding of data
onto the end of the Vaults of Parnassus URL, but this constitutes an HTTPGET
request, where the query (our additional data) is included in the URL itself.
Fetching secure Web pages using HTTPS is also very easy, provided that your Python installation supports SSL:
import urllib
# Get a file-like object for a site.
f = urllib.urlopen("https://www.somesecuresite.com")
# NOTE: At the interactive Python prompt, you may be prompted for a username
# NOTE: and password here.
# Read from the object, storing the page's contents in 's'.
s = f.read()
f.close()
Including data which forms the basis of a query, as illustrated above, is also possible with URLs starting withhttps
.
Many Web services use HTTP redirects for various straightforward or even bizarre purposes. For example, a fairly common technique employed on "high traffic" Web sites is the HTTP redirection load balancing strategy where the initial request to the publicised Web site (eg.http://www.somesite.com
) is redirected to another server (eg.http://www1.somesite.com
) where a user's session is handled.
Fortunately, urlopen
handles redirects, at least in Python 2.1, and therefore any such redirection should be handled transparently by urlopen
without your program needing to be aware that it is happening. It is possible to write code to deal with redirection yourself, and this can be done using the httplib
module; however, the interfaces provided by that module are more complicated than those provided above, if somewhat more powerful.
Given a character string from a Web service, such as the value held by s
in the above examples, how can one understand the content provided by the service in such a way that an "intelligent" response can be made? One method is by using an SGML parser, since HTML is a relation of SGML, and HTML is probably the content type most likely to be experienced when interacting with a Web service.
In the standard Python library, the sgmllib
module contains an appropriate parser class called SGMLParser
. Unfortunately, it is of limited use to us unless we customise its activities somehow. Fortunately, Python's object-oriented features, combined with the design of the SGMLParser
class, provide a means of customising it fairly easily.
First of all, let us define a new class inheriting from SGMLParser
with a convenience method that I find very convenient indeed:
import sgmllib
class MyParser(sgmllib.SGMLParser):
"A simple parser class."
def parse(self, s):
"Parse the given string 's'."
self.feed(s)
self.close()
# More to come...
What the parse
method does is provide an easy way of passing some text (as a string) to the parser object. I find this nicer than having to remember calling the feed
method, and since I always tend to have the entire document ready for parsing, I do not need to use feed
many times - passing many pieces of text which comprise an entire document is an interesting feature of SGMLParser
(and its derivatives) which could be used in other situations.
Of course, implementing our own customised parser is only of interest if we are looking to find things in a document. Therefore, we should aim to declare these things before we start parsing. We can do this in the __init__
method of our class:
# Continuing from above...
def __init__(self, verbose=0):
"Initialise an object, passing 'verbose' to the superclass."
sgmllib.SGMLParser.__init__(self, verbose)
self.hyperlinks = []
# More to come...
Here, we initialise new objects by passing information to the __init__
method of the superclass (SGMLParser
); this makes sure that the underlying parser is set up properly. We also initialise an attribute called hyperlinks
which will be used to record the hyperlinks found in the document that any given object will parse.
Care should be taken when choosing attribute names, since use of names defined in the superclass could potentially cause problems when our parser object is used, because a badly chosen name would cause one of our attributes to override an attribute in the superclass and result in our attributes being manipulated for internal parsing purposes by the superclass. We might hope that the SGMLParser
class uses attribute names with leading double underscores (__
) since this isolates such attributes from access by subclasses such as our own MyParser
class.
We now need to define a way of extracting data from the document, but SGMLParser
provides a mechanism which notifies us when an interesting part of the document has been read. SGML and HTML are textual formats which are structured by the presence of so-called tags, and in HTML, hyperlinks may be represented in the following way:
<a href="http://www.python.org">The Python Web site</a>
An SGMLParser
object which is parsing a document recognises starting and ending tags for things such as hyperlinks, and it issues a method call on itself based on the name of the tag found and whether the tag is a starting or ending tag. So, as the above text is recognised by an SGMLParser
object (or an object derived from SGMLParser
, like MyParser
), the following method calls are made internally:
self.start_a(("href", "http://www.python.org"))
self.handle_data("The Python Web site")
self.end_a()
Note that the text between the tags is considered as data, and that the ending tag does not provide any information. The starting tag, however, does provide information in the form of a sequence of attribute names and values, where each name/value pair is placed in a 2-tuple:
# The form of attributes supplied to start tag methods:
# (name, value)
# Examples:
# ("href", "http://www.python.org")
# ("target", "python")
Why does SGMLParser
issue a method call on itself, effectively telling itself that a tag has been encountered? The basic SGMLParser
class surely does not know what to do with such information. Well, if another class inherits from SGMLParser
, then such calls are no longer confined to SGMLParser
and instead act on methods in the subclass, such as MyParser
, where such methods exist. Thus, a customised parser class (eg. MyParser
) once instantiated (made into an object) acts like a stack of components, with the lowest level of the stack doing the hard parsing work and passing items of interest to the upper layers - it is a bit like a factory with components being made on the ground floor and inspection of those components taking place in the laboratories in the upper floors!
Class | Activity |
---|---|
... | Listens to reports, records other interesting things |
MyParser | Listens to reports, records interesting things |
SGMLParser | Parses documents, issuing reports at each step |
Now, if we want to record the hyperlinks in the document, all we need to do is to define a method called start_a
which extracts the hyperlink from the attributes which are provided in the startinga
tag. This can be defined as follows:
# Continuing from above...
def start_a(self, attributes):
"Process a hyperlink and its 'attributes'."
for name, value in attributes:
if name == "href":
self.hyperlinks.append(value)
# More to come...
All we need to do is traverse the attributes
list, find appropriately named attributes, and record the value of those attributes.
A nice way of providing access to the retrieved details is to define a method, although Python 2.2 provides additional features to make this more convenient. We shall use the old approach:
# Continuing from above...
def get_hyperlinks(self):
"Return the list of hyperlinks."
return self.hyperlinks
Now that we have defined our class, we can instantiate it, making a new MyParser
object. After that, it is just a matter of giving it a document to work with:
import urllib, sgmllib
# Get something to work with.
f = urllib.urlopen("http://www.python.org")
s = f.read()
# Try and process the page.
# The class should have been defined first, remember.
myparser = MyParser()
myparser.parse(s)
# Get the hyperlinks.
print myparser.get_hyperlinks()
The print
statement should cause a list to be displayed, containing various hyperlinks to locations on the Python home page and other sites.
The above example code can be downloaded and executed to see the results.
Of course, if it is sufficient for you to extract information from a document without worrying about where in the document it came from, then the above level of complexity should suit you perfectly. However, one might want to extract information which only appears in certain places or constructs - a good example of this is the text between starting and ending tags of hyperlinks which we saw above. If we just acquired every piece of text using a handle_data
method which recorded everything it saw, then we would not know which piece of text described a hyperlink and which piece of text appeared in any other place in a document.
# An extension of the above class.
# This is not very useful.
def handle_data(self, data):
"Handle the textual 'data'."
self.descriptions.append(data)
Here, the descriptions
attribute (which we would need to initialise in the __init__
method) would be filled with lots of meaningless textual data. So how can we be more specific? The best approach is to remember not only the content that SGMLParser
discovers, but also to remember what kind of content we have seen already.
Let us add some new attributes to the __init__
method.
# At the end of the __init__ method...
self.descriptions = []
self.inside_a_element = 0
The descriptions
attribute is defined as we anticipated, but the inside_a_element
attribute is used for something different: it will indicate whether or not SGMLParser
is currently investigating the contents of ana
element - that is, whether SGMLParser
is between the startinga
tag and the endinga
tag.
Let us now add some "logic" to the start_a
method, redefining it as follows:
def start_a(self, attributes):
"Process a hyperlink and its 'attributes'."
for name, value in attributes:
if name == "href":
self.hyperlinks.append(value)
self.inside_a_element = 1
Now, we should know when a startinga
tag has been seen, but to avoid confusion, we should also change the value of the new attribute when the parser sees an endinga
tag. We do this by defining a new method for this case:
def end_a(self):
"Record the end of a hyperlink."
self.inside_a_element = 0
Fortunately, it is not permitted to "nest" hyperlinks, so it is not relevant to wonder what might happen if an ending tag were to be seen after more than one starting tag had been seen in succession.
Now, given that we can be sure of our position in a document and whether we should record the data that is being presented, we can define the "real" handle_data
method as follows:
def handle_data(self, data):
"Handle the textual 'data'."
if self.inside_a_element:
self.descriptions.append(data)
This method is not perfect, as we shall see, but it does at least avoid recording every last piece of text in the document.
We can now define a method to retrieve the description data:
def get_descriptions(self):
"Return a list of descriptions."
return self.descriptions
And we can add the following line to our test program in order to display the descriptions:
print myparser.get_descriptions()
The example code with these modifications can be downloaded and executed to see the results.
Upon running the modified example, one thing is apparent: there are a few descriptions which do not make sense. Moreover, the number of descriptions does not match the number of hyperlinks. The reason for this is the way that text is found and presented to us by the parser - we may be presented with more than one fragment of text for a particular region of text, so that more than one fragment of text may be signalled between a startinga
tag and an endinga
tag, even though it is logically one block of text.
We may modify our example by adding another attribute to indicate whether we are just beginning to process a region of text. If this new attribute is set, then we add a description to the list; if not, then we add any text found to the most recent description recorded.
The __init__
method is modified still further:
# At the end of the __init__ method...
self.starting_description = 0
Since we can only be sure that a description is being started immediately after a startinga
tag has been seen, we redefine the start_a
method as follows:
def start_a(self, attributes):
"Process a hyperlink and its 'attributes'."
for name, value in attributes:
if name == "href":
self.hyperlinks.append(value)
self.inside_a_element = 1
self.starting_description = 1
Now, the handle_data
method needs redefining as follows:
def handle_data(self, data):
"Handle the textual 'data'."
if self.inside_a_element:
if self.starting_description:
self.descriptions.append(data)
self.starting_description = 0
else:
self.descriptions[-1] += data
Clearly, the method becomes more complicated. We need to detect whether the description is being started and act in the manner discussed above.
The example code with these modifications can be downloaded and executed to see the results.
Although the final example file produces some reasonable results - there are some still strange descriptions, however, and we have not taken images used within hyperlinks into consideration - the modifications that were required illustrate that as more attention is paid to the structure of the document, the more effort is required to monitor the origins of information. As a result, we need to maintain state information within the MyParser
object in a not-too-elegant way.
For application purposes, the SGMLParser
class, its derivatives, and related approaches (such as SAX) are useful for casual access to information, but for certain kinds of querying, they can become more complicated to use than one would initially believe. However, these approaches can be used for another purpose: that of building structures which can be accessed in a more methodical fashion, as we shall see below.
Given a character string s
, containing an HTML document which may have been retrieved from a Web service (using an approach described in an earlier section of this document), let us now consider an alternative method of interpreting the contents of this document so that we do not have to manage the complexity of remembering explicitly the structure of the document that we have seen so far. One of the problems with SGMLParser
was that access to information in a document happened "serially" - that is, information was presented to us in the order in which it was found - but it may have been more appropriate to access the document information according to the structure of the document, so that we could request all parts of the document corresponding to the hyperlink elements present in that document, before examining each document portion for the text within each hyperlink element.
In the XML world, a standard called the Document Object Model (DOM) has been devised to provide a means of access to document information which permits us to navigate the structure of a document, requesting different sections of that document, and giving us the ability to revisit such sections at any time; the use of Python with XML and the DOM is described in another document. If all Web pages were well-formed XML - that is, they all complied with the expectations and standards set out by the XML specifications - then any XML parser would be sufficient to process any HTML document found on the Web. Unfortunately, many Web pages useless formal variants of HTML which are rejected by XML parsers. Thus, we need to employ particular tools and additional techniques to convert such pages to DOM representations.
Below, we describe how Web pages may beprocessed using the PyXML toolkit and with the libxml2dom package to obtain a top-level document object. Since both approaches yield an object which is broadly compatible with the DOM standard, the subsequent description of how we then inspect such documents applies regardless of whichever toolkit or package we have chosen.
It is possible to use Python's XML framework with the kind of HTML found on the Web by employing a special "reader" class which builds a DOM representation from an HTML document, and the consequences of this are described below.
An appropriate class for reading HTML documents is found deep in the xml
package, and we shall instantiate this class for subsequent use:
from xml.dom.ext.reader import HtmlLib
reader = HtmlLib.Reader()
Of course, there are many different ways of accessing the Reader
class concerned, but I have chosen not to import Reader
into the common namespace. One good reason for deciding this is that I may wish to import other Reader
classes from other packages or modules, and we clearly need a way to distinguish between them. Therefore, I import the HtmlLib
name and access the Reader
class from within that module.
Unlike SGMLParser
, we do not need to customise any class before we load a document. Therefore, we can "postpone" any consideration of the contents of the document until after the document has been loaded, although it is very likely that you will have some idea of the nature of the contents in advance and will have written classes or functions to work on the DOM representation once it is available. After all, real programs extracting particular information from a certain kind of document do need to know something about the structure of the documents they process, whether that knowledge is put in a subclass of a parser (as in SGMLParser
) or whether it is "encoded" in classes and functions which manipulate the DOM representation.
Anyway, let us load the document and obtain a Document
object:
doc = reader.fromString(s)
Note that the "top level" of a DOM representation is always a Document
node object, and this is what doc
refers to immediately after the document is loaded.
Obtaining documents using libxml2dom is slightly more straightforward:
import libxml2dom
doc = libxml2dom.parseString(s, html=1)
If the document text is well-formed XML, we could omit thehtml
parameter or set it to have a false value. However, if we are not sure whether the text is well-formed,no significant issues will arise from setting the parameterin the above fashion.
Now, it is appropriate to decide which information is to be found and retrieved from the document, and this is where some tasks appear easier than with SGMLParser
(and related frameworks). Let us consider the task of extracting all the hyperlinks from the document; we can certainly find all the hyperlink elements as follows:
a_elements = doc.getElementsByTagName("a")
Since hyperlink elements comprise the startinga
tag, the endinga
tag, and all data between them, the value of the a_elements
variable should be a list of objects representing regions in the document which would appear like this:
<a href="http://www.python.org">The Python Web site</a>
To make the elements easier to deal with, each object in the list is not the textual representation of the element as given above. Instead, an object is created for each element which provides a more convenient level of access to the details. We can therefore obtain a reference to such an object and find out more about the element it represents:
# Get the first element in the list. We don't need to use a separate variable,
# but it makes it clearer.
first = a_elements[0]
# Now display the value of the "href" attribute.
print first.getAttribute("href")
What is happening here is that the first
object (being the firsta
element in the list of those found) is being asked to return the value of the attribute whose name is href
, and if such an attribute exists, a string is returned containing the contents of the attribute: in the case of the above example, this would be...
http://www.python.org
If the href
attribute had not existed, such as in the following example element, then a value of None
would have been returned.
<a name="Example">This is not a hyperlink. It is a target.</a>
Previously, this document recommended the usage of namespaces and the getAttributeNS
method, rather than the getAttribute
method. Whilst XML processing may involve extensive use of namespaces, some HTML parsers do not appear to expose them quite as one would expect: for example, not associating the XHTML namespace with XHTML elements in a document. Thus, it can be advisable to ignore namespaces unless their usage is unavoidable in order to distinguish between elements in mixed-content documents (XHTML combined with SVG, for example).
We are already being fairly specific, in a sense, in the way that we have chosen to access thea
elements within the document, since we start from a particular point in the document's structure and search for elements from there. In the SGMLParser
examples, we decided to look for descriptions of hyperlinks in the text which is enclosed between the starting and ending tags associated with hyperlinks, and we were largely successful with that, although there were some issues that could have been handled better. Here, we shall attempt to find everything that is descriptive within hyperlink elements.
Each hyperlink element is represented by an object whose attributes can be queried, as we did above in order to get the href
attribute's value. However, elements can also be queried about their contents, and such contents take the form of objects which represent "nodes" within the document. (The nature of XML documents is described in another introductory document which discusses the DOM.) In this case, it is interesting for us to inspect the nodes which reside within (or under) each hyperlink element, and since these nodes are known generally as "child nodes", we access them through the childNodes
attribute on each so-called Node
object.
# Get the child nodes of the first "a" element.
nodes = first.childNodes
Nodes are the basis of any particular piece of information found in an XML document, so any element found in a document is based on a node and can be explicitly identified as an element by checking its "node type":
print first.nodeType
# A number is returned which corresponds to one of the special values listed in
# the xml.dom.Node class. Since elements inherit from that class, we can access
# these values on 'first' itself!
print first.nodeType == first.ELEMENT_NODE
# If first is an element (it should be) then display the value 1.
One might wonder how this is useful, since the list of hyperlink elements, for example, is clearly a list of elements - that is, after all, what we asked for. However, if we ask an element for a list of "child nodes", we cannot immediately be sure which of these nodes are elements and which are, for example, pieces of textual data. Let us therefore examine the "child nodes" of first
to see which of them are textual:
for node in first.childNodes:
if node.nodeType == node.TEXT_NODE:
print "Found a text node:", node.nodeValue
If we wanted only to get the descriptive text within each hyperlink element, then we would need to visit all nodes within each element (the "child nodes") and record the value of the textual elements. However, this would not quite be enough - consider the following document region:
<a href="http://www.python.org">A <em>really</em> important page.</a>
Within thea
element, there are text nodes and an em
element - the text within that element is not directly available as a "child node" of thea
element. If we did not consider textual child nodes of each child node, then we would miss important information. Consequently, it becomes essential to recursively descend inside thea
element collecting child node values. This is not as hard as it sounds, however:
def collect_text(node):
"A function which collects text inside 'node', returning that text."
s = ""
for child_node in node.childNodes:
if child_node.nodeType == child_node.TEXT_NODE:
s += child_node.nodeValue
else:
s += collect_text(child_node)
return s
# Call 'collect_text' on 'first', displaying the text found.
print collect_text(first)
To contrast this with the SGMLParser
approach, we see that much of the work done in that example to extract textual information is distributed throughout the MyParser
class, whereas the above function, which looks quite complicated, gathers the necessary operations into a single place, thus making it look complicated.
Interestingly, it is easier to retrieve whole sections of the original document as text for each of the child nodes, thus collecting the complete contents of thea
element as text. For this, we just need to make use of a function provided in the xml.dom.ext
package:
from xml.dom.ext import PrettyPrint
# In order to avoid getting the "a" starting and ending tags, prettyprint the
# child nodes.
s = ""
for child_node in a_elements[0]:
s += PrettyPrint(child_node)
# Display the region of the original document between the tags.
print s
Unfortunately, documents produced by libxml2dom do not work withPrettyPrint
. However, we can use a method on each node object instead:
# In order to avoid getting the "a" starting and ending tags, prettyprint the
# child nodes.
s = ""
for child_node in a_elements[0]:
s += child_node.toString(prettyprint=1)
# Display the region of the original document between the tags.
print s
It is envisaged that libxml2dom will eventually work better with such functions and tools.
Recover MySQL root password (0) | 2008.02.28 |
---|---|
Beautiful Soup (0) | 2008.02.26 |
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글) (0) | 2008.02.16 |
[팁] vi editor와 관련된 유용한 팀[펌] (0) | 2008.02.14 |
알아두면 편리한 윈도우 명령어 (0) | 2008.02.14 |
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글)간단한 스파이더와 스크래퍼로 인터넷 콘텐트 모으기 |
난이도 : 중급 M. Tim Jones, Consultant Engineer, Emulex 2007 년 4 월 17 일 웹 스파이더(Web spider)는 인터넷을 크롤링 하며 정보를 수집하고, 필터링 하며, 사용자를 위한 정보를 한데 모으는 소프트웨어 에이전트입니다. 일반 스크립팅 언어와 웹 모듈을 사용하면 웹 스파이더를 쉽게 구현할 수 있습니다. 이 글에서는 리눅스�용 스파이더와 스크래퍼를 구현하여 웹 사이트를 크롤링 하며 정보를 모으는 방법을 설명합니다. 스파이더(spider)는 특정 목적을 위해 특정한 방법으로 인터넷을 크롤링(crawl) 하는 프로그램이다. 이 프로그램의 목적은 정보를 수집하거나 웹 사이트의 구조와 유효성을 파악하는 것이다. 스파이더는 Google과 AltaVista 같은 현대적인 검색 엔진의 기초가 된다. 이러한 스파이더들은 웹에서 자동으로 데이터를 검색하여, 검색어에 가장 잘 맞는 웹 사이트의 내용을 인덱싱 하는 다른 애플리케이션에 전달한다.
스파이더와 비슷한 것으로 웹 스크래퍼(Web scraper)가 있다. 스크래퍼는 스파이더의 한 유형으로서, 웹에서 제품이나 서비스 비용 같은 특수한 내용이 스크래핑 대상이 된다. 한 가지 사용 예제로는 가격 비교가 있는데, 해당 제품의 가격을 파악하여 본인 제품의 가격을 조정하고, 이에 따라 광고를 하는 것이다. 스크래퍼는 많은 웹 소스들에서 데이터를 모으고 그 정보를 사용자에게 제공한다. 스파이더의 본질을 생각할 때, 고립성이 아닌 환경과의 인터랙션에 맞추어 이를 생각하게 된다. 스파이더는 자신의 길을 보고 감지하며, 한 장소에서 또 다른 장소로 의미 있는 방식으로 이동한다. 웹 스파이더도 비슷한 방식으로 작동한다. 웹 스파이더는 고급 언어로 작성된 프로그램이며, Hypertext Transfer Protocol (HTTP) 같은 네트워킹 프로토콜을 사용하여 환경과 인터랙팅 한다. 스파이더가 여러분과 통신하기 원한다면, Simple Mail Transfer Protocol (SMTP)을 사용하여 이메일 메시지를 보낼 수 있다. 스파이더는 HTTP 또는 SMTP 로 국한되지 않는다. 일부 스파이더는 SOAP 또는 Extensible Markup Language Remote Procedure Call (XML-RPC) 프로토콜 같은 웹 서비스를 사용한다. 다른 스파이더는 Network News Transfer Protocol (NNTP)을 통해 뉴스 그룹과 소통하거나, Really Simple Syndication (RSS) 피드로 흥미로운 뉴스 아이템들을 찾는다. 대부분의 스파이더는 본질적으로 명암 강도(light-dark intensity)와 움직임의 변화만 볼 수 있지만, 웹 스파이더들은 많은 유형의 프로토콜들을 사용하여 보고 감지할 수 있다.
웹 스파이더와 스크래퍼는 유용한 애플리케이션이고, 따라서 좋든 나쁘든, 여러 가지 다양한 유형의 사용법이 있다. 이러한 기술을 사용하는 몇 가지 애플리케이션에 대해 살펴보도록 하자. 웹 스파이더는 인터넷 검색을 쉽고 효율적으로 만든다. 검색 엔진은 많은 웹 스파이더들을 사용하여 인터넷 상의 웹 페이지들을 크롤링 하고, 콘텐트를 리턴하며, 이를 인덱싱 한다. 이것이 완료되면, 검색 엔진은 로컬 인덱스를 빠르게 검색하여 검색에 맞는 가장 합당한 결과를 찾는다. Google은 PageRank 알고리즘을 사용하는데, 검색 결과의 웹 페이지 랭크(rank)는 얼마나 많은 페이지들이 여기에 링크되어 있는지를 나타내는 것이다. 이것은 투표(vote)로서도 작동하는데, 높은 투표를 가진 페이지들은 가장 높은 랭크를 얻는다. 이와 같이 인터넷을 검색하는 것은 웹 콘텐트와 인덱서를 통신하는데 있어서 대역폭과 결과를 인덱싱 하는 전산 비용 관점에서 볼 때 비용이 많이 든다. 많은 스토리지가 이와 같은 것을 필요로 하지만, Google이 Gmail 사용자들에게 1,000 메가바이트의 스토리지를 제공한다고 생각한다면 이것은 문제도 아니다. 웹 스파이더는 일련의 정책을 사용하여 인터넷 상의 흐름을 최소화 한다. Google은 80억 개 이상의 웹 페이지들을 인덱싱 한다. 실행 정책은 크롤러가 인덱서로 어떤 페이지들을 가져오는지, 웹 사이트로 가서 이를 다시 체크하는 빈도수는 어느 정도인지에 대한 politeness 정책을 정의한다. 웹 서버는 robot.txt라고 하는 파일을 사용하여 크롤러를 차단할 수 있다. 표준 검색 엔진 스파이더와 마찬가지로, 기업용 웹 스파이더는 일반인이 사용할 수 없는 콘텐트를 인덱싱 한다. 예를 들어, 기업들은 사원들이 사용하는 내부 웹 사이트를 갖고 있다. 이러한 유형의 스파이더는 로컬 환경으로 제한된다. 검색이 제한되기 때문에 더 많은 전산 파워가 사용되며, 전문화 되고 보다 완벽한 인덱스가 가능하다. Google은 한 단계 더 나아가서 데스크탑 검색 엔진을 제공하여 여러분 개인용 컴퓨터의 콘텐트를 인덱싱 한다. 콘텐트를 압축하거나 통계를 만들어 내는 등, 특수한 크롤러도 있다. 압축 크롤러는 웹 사이트를 크롤링 하면서, 콘텐트를 로컬로 가져와서 장기적인 저장 미디어에 저장되도록 한다. 이것은 백업용으로 사용될 수 있고, 더 크게는 인터넷 콘텐트의 스냅샷을 만들기도 한다. 통계는 인터넷 콘텐트와 무엇이 부족한지를 이해하는데 도움이 된다. 크롤러는 얼마나 많은 웹 서버들이 실행되는지, 특정 유형의 웹 서버들이 얼마나 많은지, 사용할 수 있는 웹 페이지 수, 깨진 링크의 수(HTTP 404 error, page not found 등을 리턴함) 등을 규명하는데 사용된다. 기타 전문적인 크롤러에는 웹 사이트 체커(checker)도 있다. 이 크롤러는 소실된 콘텐트를 찾고, 모든 링크들을 검사하며, 여러분의 Hypertext Markup Language (HTML)이 유효한지를 확인한다. 이제 어두운 쪽으로 가보도록 하자. 불행하게도, 일부 썩은 사과들이 인터넷을 망치고 있다. 이메일을 모으는 크롤러들은 이메일 주소가 있는 웹 사이트를 검색하여 대량의 스팸을 생성하는데 사용한다. 포스티니(Postini) 보고서(2005년 8월)에 따르면, 포스티니(Postini) 사용자들의 모든 이메일 메시지들의 70%가 스팸 이라고 한다. 이메일 모으기는 가장 흔한 크롤러 동작 메커니즘 중 하나이다. 이 글에서는 이 마지막 크롤러 예제를 설명한다. 지금까지, 웹 스파이더와 스크래퍼를 설명했다. 다음 네 가지 예제들은 Ruby와 Python 같은 현대적인 스크립팅 언어를 사용하여 리눅스용 스파이더와 스크래퍼를 구현하는 방법을 설명하겠다.
이 예제를 통해 주어진 웹 사이트에 대해 어떤 종류의 웹 서버가 실행되는지를 규명하는 방법을 설명하겠다. 이것은 매우 재미있고, 정부, 학계, 업계에서 어떤 종류의 웹 서버를 사용하는지도 알 수 있다. Listing 1은 HTTP 서버를 규명하기 위해 웹 사이트를 스크래핑 하는 Ruby 스크립트이다. Listing 1. 간단한 메타데이터 스크래핑을 위한 Ruby 스크립트(srvinfo.rb)
srvinfo 스크립트를 사용하는 방법을 설명하는 것 외에도, Listing 2는 많은 정부, 학계, 비즈니스 웹 사이트에서 가져온 결과들도 보여준다. Apache (68%)부터 Sun과 Microsoft� Internet Information Services (IIS)까지 다양하다. 서버가 리포팅 되지 않은 경우도 있다. 미크로네시아(Federated States of Micronesi)는 구 버전의 Apache를 실행하고 있고(이제 업데이트가 필요하다.), Apache.org는 첨단을 달리고 있다는 사실이 흥미롭다. Listing 2. 서버 스크래퍼의 사용 예제
이것은 유용한 데이터이고, 정부와 학교들이 자신들의 웹 서버로 무엇을 사용하는지를 알 수 있어서 재미있다. 다음 예제에서는 보다 덜 유용한 주식 시세 스크래퍼를 설명하겠다.
이 예제에서는, 간단한 웹 스크래퍼(스크린 스크래퍼(screen scraper))를 구현하여 주식 시세 정보를 모으도록 하겠다. 다음과 같이 응답 웹 페이지에 한 패턴을 활용하는 방식을 사용할 것이다. Listing 3. 주식 시세용 웹 스크래퍼
이 Ruby 스크립트에서, HTTP 클라이언트를 서버로 연결하고(이 경우, www.smartmoney.com), ( 주식 시세 스크래퍼를 사용하기 위해, 관심 있는 주식 심볼을 가진 스크립트를 호출한다. (Listing 4) Listing 4. 주식 시세 스크래퍼의 사용 예제
예제 2의 주식 시세용 웹 스크래퍼는 매력적이지만, 이 스크래퍼가 주식 시세를 늘 모니터링 하고, 관심 있는 주식이 오르거나 하락할 때 여러분에게 알려주도록 한다면 더욱 유용할 것이다. 기다림을 끝났다. Listing 5에서, 웹 스크래퍼를 업데이트 하여 주식을 지속적으로 모니터링 하고 주가 변동이 있을 때 이메일 메시지를 보내도록 하였다. Listing 5. 이메일 알림을 보낼 수 있는 주식 스크래퍼
Ruby 스크립트는 다소 길지만, Listing 3의 주식 스크래핑 스크립트를 기반으로 구현한 것이다. 새로운 함수 Listing 6은 주식 시세 모니터링 실행 예제이다. 2분 마다 주식이 체크되고 프린트 된다. 주가가 상한선을 넘으면, 이메일 알림이 보내지고 스크립트가 종료한다. Listing 6. 주식 모니터 스크립트 데모
결과 이메일은 그림 1과 같다. 스크립팅 된 데이터의 소스에 링크가 걸려있다. 그림1. Listing 5의 Ruby 스크립트에서 보낸 이메일 알림 이제 스크래퍼를 떠나서 웹 스파이더의 구조에 대해 살펴보도록 하자.
마지막 예제에서는 웹 사이트를 크롤링 하는 웹 스파이더에 대해 설명하도록 하겠다. 보안을 위해 사이트 밖에 머무르지 않고, 대신 하나의 웹 페이지만 탐구하도록 하겠다. 웹 사이트를 크롤링 하고, 이 안에서 제공되는 링크를 따라가려면, HTML 페이지를 파싱해야 한다. 웹 페이지를 성공적으로 파싱할 수 있다면 다른 리소스에 대한 링크를 구분할 수 있다. 어떤 것은 로컬 리소스(파일)을 지정하고, 다른 것은 비 로컬 리소스(다른 웹 페이지에 대한 링크)를 나타낸다. 웹을 크롤링 하려면, 주어진 웹 페이지로 시작하여, 그 페이지에 있는 모든 링크를 파악하고, 이들을 to-visit 큐에 대기시킨 다음, to-visit 큐에서 첫 번째 아이템을 사용하여 이 프로세스를 반복한다. 이것은 breadth-first traversal(너비 우선 순회)이다. (발견된 첫 번째 링크를 통해 나아가는 것과는 대조적이다. 이것은 depth-first behavior(깊이 우선 순회)라고 한다.) 비 로컬(non-local) 링크를 피하고 로컬 웹 페이지로만 탐색한다면 웹 크롤러에게 하나의 웹 사이트를 제공한다. (Listing 7) 이 경우, 나는 Ruby에서 Python으로 전환하여 Python의 유용한 Listing 7. Python 웹 사이트 크롤러 (minispider.py)
이 크롤러의 기본 디자인은 첫 번째 링크를 로딩하여 큐를 검사하는 것이다. 이 큐는 next-to-interrogate 큐로서 작동한다. 링크가 체크되면, 발견된 새로운 링크들이 같은 큐에 로딩된다. 먼저, Python의 두 개의 인스턴스 변수들이 이 클래스 안에 포함되는데, 여러분도 보듯, 클래스 메소드는 단순하다.
웹 스파이더를 호출하려면, 웹 사이트 주소와 링크를 제공한다.
이 경우, Free Software Foundation(자유 소프트웨어 재단)에서 루트 파일을 요청하고 있다. 이 명령어의 결과는 Listing 8과 같다. 요청 큐에 추가된 새로운 링크와 비 로컬 링크 같은 무시된 링크를 볼 수 있다. 리스팅 밑에, 루트에서 발견된 그 링크에 대한 질의를 볼 수 있다. Listing 8. minispider 스크립트의 결과
이 예제는 웹 스파이더의 크롤링 단계를 나타내고 있다. 이 파일이 클라이언트에 의해 읽혀진 후에, 페이지의 콘텐트가 검사된다.
두 개의 스크래퍼와 스파이더를 구현하는 방법을 배웠다. 이러한 기능을 제공하는 리눅스 툴도 있다. Web get을 뜻하는
웹 스파이더를 사용하는 인터넷에서의 데이터 마이닝에 대한 소송들이 있었고, 잘 처리되지 않고 있다. Farechase, Inc.는 최근 American Airlines로부터 스크린 스크래핑과 관련하여 고소를 당했다. 이 소송은 American Airlines의 사용자 계약에 위반되는 데이터를 모았다는 점이 소송에 걸렸다. 소송이 실패하자, American Airlines는 불법 침해를 주장했고 이것은 성공을 거두었다. 다른 소송 건으로는 스파이더와 스크래퍼가 합법적 사용자의 대역폭을 가져가는 것과 관련한 것이었다. 모두가 근거 있는 소송들이고 Politeness 정책들을 수립하는 것이 더욱 중요해지고 있다. (참고자료)
웹의 크롤링과 스크래핑은 재미도 있고 이롭기도 하다. 하지만, 앞서 언급한 것처럼, 법적인 문제도 있다. 스파이더링이나 스크래핑을 할 때, 서버에서 사용할 수 있는 robots.txt 파일을 준수하고, 이것을 여러분의 Politeness 정책들에 추가하도록 한다. SOAP 같은 새로운 프로토콜들은 스파이더링을 더욱 쉽게 만들고, 일반 웹 작동에는 영향을 덜 준다. 시맨틱 웹 같은 노력이 스파이더링을 더욱더 단순화 하기 때문에 스파이더링의 솔루션과 방식은 계속해서 성장할 전망이다. 교육
제품 및 기술 얻기
토론
|
Beautiful Soup (0) | 2008.02.26 |
---|---|
Python and HTML Processing (0) | 2008.02.26 |
[팁] vi editor와 관련된 유용한 팀[펌] (0) | 2008.02.14 |
알아두면 편리한 윈도우 명령어 (0) | 2008.02.14 |
윈도우에 상응하는 리눅스 프로그램 4/4 (0) | 2008.02.13 |
[팁] vi editor와 관련된 유용한 팀[펌] |
글쓴이 : 예진맘 조회 : 2,350 추천 : 0 |
이번에 굉장히 유용하게 사용했습니다. 특히 치환에 있었어 ㅠ.ㅠ 일단은 파일이 1 2 3 4 5 - 5개가 있다고 가정하겠습니다. vi 1 로 1 파일을 열었습니다. 그리고 이제 1파일에 있는 내용을 2파일에 붙여넣을때에 1이 열린상태에서 v 를 눌러서 비쥬얼 모드를 만듭니다. 그리고 블럭을 씌운후에 y 를 눌러서 복사를 합니다. :e 2 로 2파일을 엽니다. 이제 붙여넣고 싶은 부분에서 붙여넣기 p 를 누릅니다. 이젠 3 파일과 4 파일을 비교해보겠습니다. vi 3 으로 3파일을 열었습니다. 이제 그 열린 창에서 Ctrl+w+n 으로 창을 하나 분할 합니다. 그리고나서 :e 4 로 4파일을 엽니다. 이작업이 귀찮으면 그냥 :new 4 하면 창을 분할 하면서 파일이 열립니다. ※ :new 없는 파일명 이면 새로운 문서의 편집이 됩니다. 두 창간의 이동을 위해서 Ctrl+w+w 를 눌르면서 윗창과 아래창을 번갈아가면서 이동해서 비교해 봅니다. 이렇게 비교할때에는 여러가지 복잡하기 때문에 내가 보던 곳이 헷갈릴때가 있습니다. 그럴때에는 mark 기능을 사용하기 위해서 내가 기억해둘 곳에다가 m,a (m을 누르고 a를 누르라는 말입니다.) 그러면 a키에 그 위치가 저장되는 것입니다. 그러니 m,b 를 누르면 b에 저장되는 것이지요. 여러곳을 마크해놓고 작업을 하다가 생각날때에 그 마크 해두었던 위치로 가는데 갈때에는 왼쪽 맨 모서리 esc밑의 키인 `,a 를 누릅니다.(`를 누른후 지정한 마크키 a를 누르란 말입니다.) 그러면 그 위치로 커서가 가게 됩니다. 뭐 이런식으로 인간의 기억력의 한계를 극복합니다.^^; 보통 마크 해놓은 곳부터 지금 보는 곳 까지를 블럭 씌울때에 사용하는데 그럴때는 마크 a를 해놓고, 블럭을 씌우고 싶은 곳 마지막에서 v 를 누러놓고, `,a 를 눌러서 블럭을 쭉 씌웁니다. 잘라내서 붙여넣고 싶을때가 있는데 그럴때는 블럭 씌워졌을때에 d 를 누르면 삭제가 되면서 버퍼에 저장이 되어 있습니다. 원하는 곳에서 p를 누르면 붙여넣기가 되죠^^ 붙여넣기와 복사 두가지를 골고루 사용해야 되니깐요~ ※여기서 주의할 점은 한 창, 그러니깐 한 문서에 마크는 따로 설정된다는 것 입니다. 작업하다보면 파일을 저장할때에 4 파일을 작업하다가 그 파일을 1파일로 저장하고 싶을때가 있습니다. 그럴땐 :w! 1 로 강제저장을 해줍니다. 그리고 그 보던 파일을 복사 해버리고 싶으면 :w 새파일명 을 해주면 그 보던 문서와 동일한 내용의 파일이 생깁니다. 창을 열어서 사용할 때의 팁들을 정리해보자면, 창은 계속해서 새로 열어서 작업을 할 수 있습니다. 그리고 그 창들은 하나의 문서를 열었을때 처럼 작업을 할 수 있습니다. 여러 창들을 열다보면 글씨가 깨져 보일 수도 있습니다. 이럴때는 Ctrl+l 을 눌러서 화면을 리플래쉬 합니다. 그리고 창이 여러개일때엔 창의 이동을 아래위로 해야 할때가 있습니다. 그땐 그냥 이동을 할때처럼 쓰는 것처럼 j는 아래로 k는 윗쪽방향인데, 창의 이동에서도 같습니다. Crtl+w,j 를 하면 아래창으로 이동하고 Ctrl+w,k 를 하면 윗창으로 이동합니다. 그게 불편하신 분은 이동시 방향키도 가능하니깐 ctrl + w , 위쪽;아래쪽 이런식으로 사용하셔두 됩니다. 또한 창을 잡다하게 많이 열었다면 그냥 하나의 창만 보고 싶을때엔 Ctrl+w,o 를 눌러주면 창이 하나로 통합 되어집니다~ 크게 하려면 ctrl + w , + 키 작게 하려면 ctrl + w , + 키 현재창을 닫기을 때에는 ctrl + w, c 현재 창을 최대화 하려면 ctrl + w + _ 열려있는 창들의 크기를 모두 같게 하려면 ctrl + w + = ※ 파일명 입력시 tab 키로 파일명 완성하기가 먹힙니다. 그리고 저 같은 경우에 서버간의 이동을 하다보면 홈페이지의 주소를 전체적으로 싹 바꿔주어야 할 경우가 자주 생깁니다. 그럴때에 123.123.123.123 아이피를 321.321.321.321 로 바꿀때엔... :%s/123.123.123.123/321.321.321.321/g 이런식으로 해주면 변환해줍니다. :%s/찾을말/바꿀말/g <= 그 문서전체적으로 교체해줍니다. 뭐 다른 예로 첫머리 부분에 주석처리를 하고 싶을 때에는 :%s/\(.*\)/#\1/g 이런식으로 활용을 하면 편합니다~ 생소하셔할 분들이 있을 텐데; 이렇게 옵션을 줄때에 기호를 쓰면 \으로 구분을 해주어야 합니다. \( 이렇게 하면 (를 인식하는 거죠~ 그냥 간단히 단어를 찾고 싶을때엔 그냥 / 나 ? 를 눌른다음에 찾고싶은 단어를 적어서 엔터를 치면 그 단어가 블럭이 쳐져서 나타납니다. 만약 대소문자 구분없이 단어를 찾고 싶을 때는 :set ic 나 :set ignorecase 해 주면 대소문자를 가리지 않습니다. /는 검색방향이 아랫쪽이고 ?는 검색방향이 윗쪽입니다. 여러단어가 걸렸을때에 다음 단어로 가고 싶을 때에는 n 과 N 이 있는데 n은 아랫쪽 방향으로 다음 단어로 가는 것이고 N 은 윗쪽 방향으로 다음단어로 가는 것입니다. 자주 쓰는 팁이죠^^; ※ 찾기 기능을 써서 블럭이 씌워져 있을 시에 그 블럭을 지우고 싶다면 :set nohlsearch 이라고 해주시면 블럭이 사라집니다. 이러한 기능들은 편집때 주로 쓰고, 이제 문서를 생성해서 작성할때에는 이러한 팁들을 사용합니다. 함수나 변수명을 타이핑하다가 ^p 나 ^n 을 눌러 보세요. 자동으로 완성됩니다. 예를 들어서, pknowhow 와 linuxnew 의 운영자는 pbi12 입니다. 이때에 pk만 치고 ^p를 누르게 되면 자동으로 그 문서안에서 같은 단어를 검색해서 자동완성 해줍니다. li 만치고 ^n 을 누르더라도 자동완성 됩니다. 다른 점이라면 위아래 검색순서가 다릅니다^^ 여러개의 단어가 겹칠땐 ^p 나 ^n 을 여러번 눌러 동일한 자동완성을 찾으면 됩니다~ 그리고 소스를 다 작성했는데 빈줄이 너무 많아서 하나씩 지우기 귀찮을때는 :g/^$/d 이런식으로 문서내의 모든 빈줄을 삭제해 줍니다. 그리고 작성하다가 중요한 부분은 파일로 저장하고 싶을때에는 Shift+v를 눌러서 행블럭을 씌운후에 범위를 정한다음에 : 를 눌러서 :'<,'>w 파일명 이런식으로 파일로 저장도 합니다. ※ :'<,'> 는 블럭을 씌우고 : 를 누르면 자동으로 붙습니다. 글쓰다가 보면 키를 잘못 눌르거나 커서가 더이상 움직이지 않을때에 띡띡하고 소리가 나게 됩니다. 이 소리가 듣기 싫으실때에는 :set vb 라고 쳐주면 소리가 나지 않습니다. 그리고 보통 C 소스 짜다가 보면 확인해 보고 싶을때는 그냥 간단한 쉘 명령어를 써서 a.c 소스를 a로 컴파일 한다면 :!gcc -o a a.c 호 해서 a파일을 만들고 a를 실행해 보기 위해서 :!./a 라고 쳐서 대략 확인을 합니다. 만약 몇 번째 라인에서 에러가 났다고 나오면 :set nu 라고 쳐서 라인 번호가 나오게 만들어 준다음에 :라인번호 라고 쳐서 그라인으로 이동을 한후에 수정을 해줍니다. 다시 컴파일 하기 위해서는 :w 로 바뀐내용을 저장해준 후에 다시 컴파일하면 됩니다. C언어 함수에 대한 설명이나 명령어들의 man 페이지등을 보고 싶을때에는 그 단어에 커서를 두고서 shift+k 를 누르게 되면 설명이 man페이지로 나오게 됩니다. man 페이지는 그냥 커서로 아래위로 움직여서 참고해서 보고 q로 종료하면됩니다. 그런데 영어실력이 딸려서 영어 단어가 궁금할때에는 어떤분이 짜신건데, 이 스크립트를 이용해서 영어단어를 찾아서 바로 검색할 수 있습니다. 스크립트 소스 ====================================================================================== #!/bin/sh WORD=$* WORD=$(echo ${WORD} |od -tx1 -w1000 |head -1|sed -e 's/^[0-9]\+ //' -e 's/ 0a$//' -e 's/20/+/g' -e 's/ /%/g' -e 's/^/%/'|tr '[a-z]' '[A-Z]') clear lynx -nolist -verbose -dump http://kr.engdic.yahoo.com/result.html?p= ${WORD} |grep -v "bu2.gif" |tail +14 |tac |tail +11 |tac |sed -e 's/[phon[0-9]\+\.gif] //g' -e 's/[phon[0-9]\+\.gif]//g'|less -r ====================================================================================== 저기 #! 여기부터 lynx 까지 복사하셔서 > cat > edic.sh > 여기서 붙여넣기 > Ctrl+d 를 눌러서 저장,종료를 하고 >chmod 711 edic 를 해서 edic 라는 파일에 실행 퍼미션 1을 줍니다. 그럼 스크립트는 완성이 되는 것이구요. 그것을 vi 쓰다가 :!edic 모르는 단어 식으로 그냥 쓰고 있습니다. ※ 여기서 주의하실 점은 edic 이라는 스크립트파일이 path가 걸려있던지 아님 문서작성하는 곳과 같은 곳에 있어야 합니다. 그리고 이 원리가 lynx를 통해서 야후의 사전을 찾아서 뿌려주는 것밖에는 아니므로 lynx가 반드시 설치되어있어야 합니다. lynx는 텍스트 브라우져로써 남의 서버에서 계정을 받아사용하시는 분들중에 몇몇 분은 서버에 안깔려있을 수도 있을꺼라고 생각됩니다. lynx를 서버관리자에게 설치해주라고 문의하셔서 설치된곳에서 해보세요^^ 그리고 소스 작성시에 지역변수와 전역변수가 있는데 그 변수들을 구분해서 찾아주는 기능도 있습니다. 소스에 너무 긴 변수명이 있다면 찾기에도 불편하니깐 바로바로 이동도 할 일이 생기게 됩니다. gd 는 local definition 으로 점프하게 되고, gD 는 global definition 으로 점프하게 됩니다. 정의된 부분을 back 할 때는 `` 을 누르면 되돌아가게 됩니다. 윗쪽 아랫쪽으로 검색을 더 하고자 할때는 # 이나 * 를 눌르면 됩니다. 이젠 소스를 다 작성하였는데 모양이 정렬이 잘 안되어 있을때에는 전체 열을 이동시켜서 모양을 맞춰줄 수 있습니다. << 라고 치면 한줄만 탭단위로 왼쪽으로 이동하게 되고 >> 한줄만 탭단위로 오른쪽으로 이동하게 됩니다. 정렬하고 싶은 부분을 블럭을 씌우고 << 해줘서 정렬을 해줄 수도 있습니다. * 이부분 강추입니다^^ 특히 강력한 기능인 Ctrl+v 열 블럭 씌우기!! 행 블럭을 씌우는게 아니라 열블럭을 씌우는 것입니다. 저도 이거 있는줄 몰랐다가 여러가지 키 조합하다가 알아냈는데 정말 편합니다. 정렬할때에 쓰면 더 좋죠~ 열로 블럭을 씌워서 정렬할 부분 << 해주어서 옮기는 거죠~ 마지막으로 more 나 less 로 문서를 보고 있다가 바로 vi로 가서 보던걸 편집하고 싶을때에는 : 로 정지한 상태에서 v 를 눌러서 vi로 전환할 수 있습니다. 이로써 pbi12, 즉 제가 작업하는 스타일이였습니다. 아직도 많이 부족하지만 정리해서 적어봤습니다. <제 2 부 막강한 여러가지 팁들> 간단히 강력한 추천하고픈 vi 팁들을 적을려고 합니다. 1부에서도 말했었던 강력한 기능인 Ctrl+v 열 블럭 씌우기!! 정말 좋습니다. -그리고 더 강력한 기능!! vi에선 전에 한 작업은 기억이 되어져 있습니다. 그래서 . (점)을 누르면 그대로 다시 됩니다. 바로 전 작업이 기억 되어져서 다시 되는 거죠^^ 만약에 아래와 같이 쳤다면 i로 삽입 모드를 만들고 pbi12 ZZang 엔터 이렇게 했다면 esc를 눌러서 모드를 변경후에 .를 누르면 pbi12 ZZang 이 쳐지고 엔터가 들어간다는 거죠~ 제가 이제야 알게 되서 그런지 무척이나 신기했습니다^^ -자~ 주목하십시요!! vi의 하이라이트인 알파벳 대문자 시리즈 입니다. ^_^ 알파벳의 대문자에 정말 좋은 기능들이 있었구요~ 그리고 소문자와 비교했을때에 비슷한 기능도 많이 있었습니다. 전 물론 그냥 키 하나 씩 눌러서 무슨 기능인가 확인해보구 적는 것이구요;; 대문자와 소문자를 잘 구분해서 보시기 바랍니다~ vi에서 w는 단어의 앞부분씩 이동이고, b는 w와 같으나 뒷쪽방향으로 이동이고, e는 단어의 끝부분씩 이동합니다. x는 지우기 i는 현재위치에서 삽입 a는 현재글자의 뒷쪽에 이어서 삽입, u는 되살리기 P 는 빈줄 한줄씩 삽입 K는 man 페이지 찾기 모드 V 는 한줄 블럭씌우기 R 은 replace 로 수정모드 A는 그 줄의 마지막에서 삽입 O는 한줄 건너뛰어 삽입 S는 그 줄 새로 작성, 현재 줄 지워지고 삽입모드 C는 현재의 줄만 현재 커서의 뒷부분이 지워지고 삽입모드 Y 한줄 복사 D는 그 줄의 처음에 있을때 누르면 한줄 삭제 (삭제되면서 버퍼에 저장 잘라내기라고 봐두 됨) p는 아래로 붙여넣기인반면 P는 위로 붙여넣기 ZZ 저장하고 종료;; x는 지우기로 del키와 같지만 X는 백슬래쉬 처럼 뒤로 지워집니다. gg 는 맨 처음으로 이동 G 는 맨 마지막으로 이동 $은 그 줄 마지막으로 이동 0은 그 줄 처음으로 이동 ^는 줄의 처음으로 이동 * 이 기능도 강력합니다^^ J 는 그 현재 위치 다음의 빈 줄을 다 붙여줍니다. 한 문장으로 만들때 사용하면 좋습니다. -자~ 여기부터는 문서를 볼때에 중요한 스크롤 기능에 대한 팁들입니다~ 문서를 빨리 더 정확하게 스크롤해서 중요부분만 보는게 중요하다고 자부하시는 분들은 꼭 보시길 바랍니다~ 대문자 L 은 그 보는 페이지에서 맨 아랫줄 커서 이동 B는 앞쪽으로 빈칸 단위로 이동 W는 뒷쪽으로 빈칸의 앞쪽 단위로 이동 E는 뒷쪽으로 빈칸의 뒷쪽 단위로 이동 [[ 는 문서의 맨처음 ]] 는 문서의 맨 마지막으로 커서 이동 Ctrl+d 반페이지씩 아래로 스크롤 Ctrl+u 반페이지씩 위로 스크롤 Ctrl+f 한페이지씩 아래로 스크롤 Ctrl+b 한페이지씩 위로 스크롤 ` 레지스트 기록한곳으로 이동 할때에 `저장문자 ( 한 단락위로 ) 한 단락 아래로 -이 외에 자주 쓰는분은 잘 아시는 참 특이하면서도 기특한? 팁들이 있습니다^^; Ctrl+g 를 눌르면 그 문서의 커서가 위치한 곳의 정보가 아랫줄에 나옵니다. *이 기능도 강추입니다!! ~ 는 대소문자 변환 블럭을 씌워 ~눌르면 대소문자가 뒤바뀜 !! 쉘명령어 사용 결과값만 :! 쉘명령어 사용 실행과정부터 나옴 |
Python and HTML Processing (0) | 2008.02.26 |
---|---|
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글) (0) | 2008.02.16 |
알아두면 편리한 윈도우 명령어 (0) | 2008.02.14 |
윈도우에 상응하는 리눅스 프로그램 4/4 (0) | 2008.02.13 |
리눅스에 파이썬 설치하기 (0) | 2008.02.13 |
리눅스에서 웹 스파이더(Web spider) 구현하기 (한글) (0) | 2008.02.16 |
---|---|
[팁] vi editor와 관련된 유용한 팀[펌] (0) | 2008.02.14 |
윈도우에 상응하는 리눅스 프로그램 4/4 (0) | 2008.02.13 |
리눅스에 파이썬 설치하기 (0) | 2008.02.13 |
sql 학습 (0) | 2008.02.13 |
| |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
글쓴이 : 화산폭발 날짜 : 06-11-11 15:20 조회 : 2404 | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
9) 과학, 특수 프로그램 10) 에뮤레이터 11) 기타
|
[팁] vi editor와 관련된 유용한 팀[펌] (0) | 2008.02.14 |
---|---|
알아두면 편리한 윈도우 명령어 (0) | 2008.02.14 |
리눅스에 파이썬 설치하기 (0) | 2008.02.13 |
sql 학습 (0) | 2008.02.13 |
MySQL5 (0) | 2008.02.05 |
알아두면 편리한 윈도우 명령어 (0) | 2008.02.14 |
---|---|
윈도우에 상응하는 리눅스 프로그램 4/4 (0) | 2008.02.13 |
sql 학습 (0) | 2008.02.13 |
MySQL5 (0) | 2008.02.05 |
정규표현식 기초 (0) | 2008.02.01 |
윈도우에 상응하는 리눅스 프로그램 4/4 (0) | 2008.02.13 |
---|---|
리눅스에 파이썬 설치하기 (0) | 2008.02.13 |
MySQL5 (0) | 2008.02.05 |
정규표현식 기초 (0) | 2008.02.01 |
에디터용 글꼴 (0) | 2008.02.01 |