- 积分
- 70
- 实力分
- 点
- 金钱数
- 两
- 技术分
- 分
- 贡献分
- 分
|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有帐号?注册会员
x
见到坛子上的兄弟们从网站上下载诸如区号,公交车等信息的时候,手工或半手工操作,非常之累,于是试着用unix下的一些传统工具写了些小东西,可以比较方便的完成这一工作。
在这里要说明一下,由于这种方法会自动大量的连接到一个网站上,可能会给该网站造成一定的负担,所以请慎重使用。
正好在弄一个重庆的公交数据,就用这个做例子,也许会加上些其它的例子,例如上次的区号数据,希望能有帮助。
在处理文本方面,unix系列下的一些软件还是非常好用的,但是一般人用的都是windows,不可能为了这个弄个unix/linux之类的,所以我找了这些软件的windows版本,并且是在windows xp下完成,让大部分人能够使用这个方法。
废话少说,正题:
因为这个用到了windows xp的命令行里面for命令的/l参数,这个参数似乎在2000的时候还是没有的,大家可以试一试。
还有几个小软件(wget,grep,sed,awk),常用linux的人一定会觉得很熟悉,这里用的是它们的windows版本,我在后面会打包传上来,包括它们要用到的dll文件,这个有些大,郁闷(我打包的批处理里面只是从100到129的,只是作为示范而已)。
好了,现在分析我们的问题:
在我的这个例子中,我打算从http://chongqing.8684.cn下载重庆的公交数据,如下图,然后做成http://mobile.0110.cn/viewthread.php?tid=151942要求的那样,我们使用其中的重庆公交线路查询(也就是第一个)。
先试一个,比如我们输入1,查询的结果如图:
注意地址栏,这是个get方法提交的表单,是比较简单的,如果你不太清楚get方法提交的表单是什么意思,那么查看一些html表单方面的文章应该很容易知道,在这里不需要知道它是什么意思,只要知道,用http://chongqing.8684.cn/so.php?k=pp&q=1就可以得到重庆公交线路--1的详细信息了,同样的,将最后的1替换成其它的数字,就可以得到其它的查询结果了。
1、下载:
现在用wget就可以把这些页面下载下来了,这个很容易(其实用flashget这样的工具也是可以批量下载下来的,不过多线程对这样的小的页面来说,没什么意义,而且不能在批处理里面自动处理,并没有什么太大的优势,而且还有另外一个原因,那就是,我没有发现flashget如何下载使用post提交的表单的方法,而wget可以,具体可以到搜索引擎上找找,或者回帖问,我举个例子说明)。
前面说到我们用到了for的一个参数/l,在windows xp下的命令行提示符中输入for /?很容易看到这个的说明:
- FOR /L %variable IN (start,step,end) DO command [command-parameters]
- 该集表示以增量形式从开始到结束的一个数字序列。
- 因此,(1,1,5) 将产生序列 1 2 3 4 5,(5,-1,1) 将产生
- 序列 (5 4 3 2 1)。
复制代码
这样我们可以用for加上wget来下载很多连续的页面。如果想在do后面执行多个命令,可以用{}来把命令括起来(后面会看到,具体不多说)。
我们这里用到的wget的参数不太多,列在下面,更多的参数可以上网去查:
- -o 将下载时的log文件输出到其它的文件中,我们的想法是,让这些工作尽量自动完成,所以我们不需要看这个输出,我们将这些输出定向到nul,就是什么都不输出,但是如果你想看下载的情况的话,可以不用这个参数,这时候log会在屏幕上输出,或者是将其写入其它的文件,需要看的时候直接看那个文件,或者使用tail(可以搜索tail的使用方法)
- -O 将下载的文档写到一个指定文件名的文件中,我们用它来给下载的页面命名
复制代码
所以,我们用来下载的命令就很明显了:
建立一个批处理,假如叫“公交.bat”,内容如下:
- @echo off
- for /l %%i in (1,1,999) do (
- wget "http://chongqing.8684.cn/so.php?k=pp&q=%%i" -o nul -O %%i.html
- )
复制代码
这里我们用@echo off把批处理的输出关闭,自动完成的时候我是没兴趣看这个屏幕的。在批处理中要用两个%,而不像在普通的命令中使用一个。
我们这里还会加入其它的命令,现在只是一步一步的分析,最后用的文件会在附件中给出。
我们查看刚刚查询的页面:http://chongqing.8684.cn/so.php?k=pp&q=1查看它的代码,我们可以看出,有用的部分是:
- <div id="resultTxt">
- <h4>学生专线1</h4><div>公交一公司 (每天6:00一班) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("大石坝|大庆村|航天职大|建材市场|花卉园|红旗河沟|海关|渝北|观音桥|华新街|上清寺|大礼堂|大溪沟|黄花园|一号桥|临江门|小什字|重庆饭店|朝天门",""));</script>(共19站)</div></div>
复制代码
这些一共是两行,我们不打算把所有的页面下载下来之后一直保存,那样有些废存储空间,所以我们只要这两行,把它们提出来,放在一个单独的临时文件中,所以上面的代码就被改为:
- @echo off
- for /l %%i in (1,1,999) do (
- wget "http://chongqing.8684.cn/so.php?k=pp&q=%%i" -o nul -O %%i.html
- grep -A1 "<div id="resultTxt">" %%i.html >>公交1.tmp
- del %%i.html
- )
复制代码
这个地方用到了另外的一个程序,grep,这个是可以在一个文本文件中挑出一部分来显示的,通过重定向把这些内容写到公交1.tmp中。
这个地方用到了一个参数:
- -A 用它可以显示符合条件的行后的指定数量的行,这里我们指定了1行,加上原先的,就是两行。
复制代码
而引号中间的没太多要说的,只是在"前用了\,学过C语言的应该都知道,转义符。
后面的del意图很明显,就不用说了。
这个时候我们为了说明问题,就不把1到999的内容都下载下来了,简单的用100到129的30个数据说明问题就可以了,用这样的方法得到100到129对应的公交1.tmp,用记事本打开,可以看到类似下面的数据:
- <div id="resultTxt">
- <li>没有<strong>100</strong>这条线路,请选择准确线路!</li></p></div>
- <div id="resultTxt">
- <h4>101</h4><div>公交一公司 (6:00--21:00) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城",""));</script>(共11站)</div></div>
复制代码
这里面的<div id="resultTxt">是不需要的,而且在这里,对应有用数据的每行开始的<h4>也是不需要的,所以我们把它们统一起来,认为是每行开始的那个用"<"和">"括起来的部分是不要的。我们这里使用sed来删除这些数据,这里我们用替换命令:
- sed "s/^<[^<]*>//g" 公交1.tmp
复制代码
这里引号中间的部分用到了正则表达式,正则表达式比较复杂,有兴趣的去查一下相关的资料,这里只解释这个地方用到的,其它的只要去找找,很容易就找到的。
这里的意思如下:
s表示替换,三个"/"之间的部份是替换的内容,将前面的内容替换成后面的(这个地方是空的,就是删除),而前面两个"/"间的内容的意思解释如下:
- ^ 表示行首
- < 就是本身的含义
- [^<] 在[和]之间的^不是行首的意思,而是表示否定,这整个部分表示不是<的字符(为什么这么用,后面马上会说)
- * 表示前一个式子的零次或者更多次
- > 和前面的<一样,表示本身
复制代码
这样,我们就去掉了每个行首的<>括起来的部分。
在这个地方我们用了[^<],在sed中,如果使用.*来表示的话(任意字符0次或以上)那么会找最长的,就是说,会替换到这一行最后一个">"符号处,这显然不是我们要的,所以我们指定非"<"的符号。
后面我们会用sed来处理很多的数据,但是这里的数据会出现在屏幕上,然后就没了,我们可以把它保存到一个文件中,就像上面的grep命令中一样,但是我不太想用太多的临时文件,于是我使用了管道,把前一个命令的输出转到后一个命令的输入中,而sed命令可以用-e来直接执行命令,不加后面的文件(具体的使用,后面就可以看到了)。
除去了这些之后,我们得到的文件是这个样子的:
- 没有<strong>100</strong>这条线路,请选择准确线路!</li></p></div>
- 101</h4><div>公交一公司 (6:00--21:00) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城",""));</script>(共11站)</div></div>
- 102</h4><div>公交一公司 (5:30--22:00) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("上请寺|隧道口|菜园坝|石板坡|南纪门|药材市场|储奇门|望龙门|道门口|朝天门",""));</script>(共10站)</div></div>
复制代码
这里面有一些空行,和一些无用的数据(没有<strong>100</strong>这条线路,请选择准确线路!</li></p></div>),我们用sed把它删掉,这样上面的那个命令就变成:
- sed "s/^<[^<]*>//g" 公交1.tmp | sed -e "/^没有.*这条线路,请选择准确线路\|^\s*$/d"
复制代码
这里需要说明的是后面部分的“/^没有.*这条线路,请选择准确线路\|^\s*$/d",.*不用多说了,任意字符的0次或以上,"\|"其实是"|",表示或者的关系,就是说,复合后面的行也要处理,只是因为"|"有了另外的含义,所以用"\"来转义。而后面的"^\s*$里面的^是行首,$是行尾,而\s表示空白符,比如空格,Tab之类的,"*"是0次或以上,所以^\s*$就表示只含有空白符的行或者空行。而后面的d表示把这样的行删除。
于是,我们可以看到如下的结果:
- 101</h4><div>公交一公司 (6:00--21:00) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城",""));</script>(共11站)</div></div>
- 102</h4><div>公交一公司 (5:30--22:00) <script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("上请寺|隧道口|菜园坝|石板坡|南纪门|药材市场|储奇门|望龙门|道门口|朝天门",""));</script>(共10站)</div></div>
复制代码
这个时候的数据,所含的杂质就不太多了。
我们可以看出,在</h4>之后的<div>公交一公司 (6:00--21:00) <script>中包含有公交数据格式要求的起止时间,但是不太复合要求,我们要把它变得符合要求,我们显然要留下括号中的东西,并且把“--”替换成“-”,以复合要求,并且把“<div>公交一公司 (6:00--21:00) <script>”里面括号的部分保留,所以我们把前面的命令变成:
- sed "s/^<[^<]*>//g" 公交1.tmp | sed -e "/^没有.*这条线路,请选择准确线路\|^\s*$/d" | sed -e "s/--/-/g" | sed -e "s/<div>[^(]*(\([^(]*\))[^(]*<script>/<div>\1<script>/g"
复制代码
这个地方要说的可能是最后的那个部分,似乎有些长,解释如下:
- <div> 很容易理解
- [^(]* 为了指定<div>和括号之间的内容,这部分内容是不要的。后面的[^(]也是一样。
- (\([^(]*\)) 这个似乎有些麻烦了最外面的“(”和“)”含义就是括号,括号里面的内容才是我们要的,这两个括号也不要,而这两个括号之间的“\(”和“\)”则是有特殊含义的,表示它们之间的内容作为一个整体,并且在后面会用上(用形如“\1”来表示,如果是有两个这样的情况,那就分别是“\1”,“\2”,依此类推。而中间的[^(]意义就很明显了,非"("的字符,和前面的[^<]类似了。
复制代码
现在的数据就是:
- 101</h4><div>6:00-21:00<script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城",""));</script>(共11站)</div></div>
- 102</h4><div>5:30-22:00<script>document.write(pp("g","公交一公司"));</script></div><div id=k><script>document.write(p("上请寺|隧道口|菜园坝|石板坡|南纪门|药材市场|储奇门|望龙门|道门口|朝天门",""));</script>(共10站)</div></div>
复制代码
我们的目的达到了。
那么我们看,如果我们不看“<”和“<”之间的部分,那么这些有用的数据被分成这样的几个段(以101为例):
- 101
- 6:00-21:00
- document.write(pp("g","公交一公司"));
- document.write(p("汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城",""));
- (共11站)
复制代码
显然对于我们来说,第一个,第二个,第四个是有用的,对于这样的情况,awk处理起来是很容易的。
awk可以指定特定的符号来把文本分成几个段,这里我们用所有的“<”和“>”之间的部分来作为分割符。
简单的awk教程可以看下面这三个链接:
http://www-128.ibm.com/developerworks/cn/linux/shell/awk/awk-1/
http://www-128.ibm.com/developerworks/cn/linux/shell/awk/awk-2/
http://www-128.ibm.com/developerworks/cn/linux/shell/awk/awk-3/
或者只是在这里简单的了解一下:
awk可以把它的命令放到一个文件中,我在这里用了order.awk这个名称。在这个文件中,可以用BEGIN{}来初始化一些东西,我们指定分割符的操作就在这里,如上面说的,所有的“<”和“>”之间的部分来作为分割符,所以就是:
- BEGIN{
- FS="(<[^<]*>)+"
- OFS="*"
- }
复制代码
这个地方的OFS是另外一个作用,在这一并说明,它的作用是:在print的时候,用来填充用“,”分割的变量的,这里是用*号填充,当然,不这样,而直接用"*"也是可以的。
我们上面说了,只要第一个,第二个,第四个,而且按照公交数据的要求,应该是第二个在最后,在awk中很容易实现:
很容易吧,这个地方的“,”会被前面指定的OFS的字符替换,而"@"则会在第四段和第二段之间。现在的数据是这样的:
- 101*document.write(p(汽车北站|海关|渝北|观音桥|大兴村|茶园|五里店|大湾|茅家山|上横街|江北城,));@6:00-21:00
复制代码
已经很类似了吧,但是还有一些数据是没用的,而且注意看下面这行:
- 113*document.write(上行: +p(五里店|茶园|大兴村|观音桥|渝北|海关|红旗河沟|花卉园|建材市场|航天职大|大庆村|大石坝|石门|肿瘤医院|沙杨路|沙中路|半月楼|陈家湾|沙坪坝|火车北站,));@6:00-20:30
复制代码
上行……晕了吧,这个地方是有上行下行的区分的,明显经过这样的处理,下行部分丢失了,我们看看“公交1.tmp”中对应的部分,就知道,在以(<[^<]*>)+为分割的时候,有上行下行之分的数据比其它的数据多了一段,我们要把这两个合并成一段,并且处理成要求的格式。
并且,现在的数据中用来分割站点的东西是“|”,而不是“*”,所以我们在用awk之前还要再用sed处理一下,把上面用到的sed代码改成如下:
- sed "s/^<[^<]*>//g" 公交1.tmp | sed -e "/^没有.*这条线路,请选择准确线路\|^\s*$/d" | sed -e "s/--/-/g" | sed -e "s/<div>[^(]*(\([^(]*\))[^(]*<script>/<div>\1<script>/g" | sed -e "s/上行.*\(+p([^(]*))\).*\(+p([^(]*))\)/\1#\2/g" |sed -e "s/p(\|,\|;\|document\.write(\|))\|+\|\x22//g" | sed -e "s/|/\*/g" >公交2.tmp
复制代码
这个部分的内容可以自己看看,如果有不明白的,可以回帖子问,我会尽量回答的。正则表达式解释起来很费劲,但是懂了就会很方便。
这样,我们用sed处理的部分的最后代码就得到了,并且把数据放到了“公交2.tmp”中,我们现在可以删除“公交1.tmp”了,不过也可以留到最后去删,不着急:)
唯一需要说明的是\x22,这个其实就是引号的十六进制表示,在windows的命令行中处理引号存在一定的问题,所以我用了这个方法来解决。
执行“order.awk”文件来处理“公交2.tmp”的方法是:
我们的批处理内容确定下来了:
公交.bat
- @echo off
- for /l %%i in (1,1,999) do (
- wget "http://chongqing.8684.cn/so.php?k=pp&q=%%i" -o nul -O %%i.html
- grep -A1 "<div id="resultTxt">" %%i.html >>公交1.tmp
- del %%i.html
- )
- sed "s/^<[^<]*>//g" 公交1.tmp | sed -e "/^没有.*这条线路,请选择准确线路\|^\s*$/d" | sed -e "s/--/-/g" | sed -e "s/<div>[^(]*(\([^(]*\))[^(]*<script>/<div>\1<script>/g" | sed -e "s/上行.*\(+p([^(]*))\).*\(+p([^(]*))\)/\1#\2/g" |sed -e "s/p(\|,\|;\|document\.write(\|))\|+\|\x22//g" | sed -e "s/|/\*/g" >公交2.tmp
- awk -f order.awk 公交2.tmp >公交.txt
- del *.tmp
- @echo on
复制代码
无需多解释了吧?
这个时候的数据是这个样子的:
- 101</h4><div>6:00-21:00<script>pg公交一公司</script></div><div id=k><script>汽车北站*海关*渝北*观音桥*大兴村*茶园*五里店*大湾*茅家山*上横街*江北城</script>(共11站)</div></div>
复制代码
除了作为分界的内容和不要的段之外,没有其它的无用内容了,我们的order.awk的内容也就可以确定下来了:
order.awk:
- BEGIN{
- FS="(<[^<]*>)+"
- OFS="*"
- }
- {
- print $1,$4 "@" $2;
- }
复制代码
好了,我们可以用这个批处理来自动完成我们的事情了,把wget.exe,grep.exe,sed.exe和awk.exe放在PATH指定的目录内或者干脆和“公交.bat”还有“order.awk”放在一个目录下,然后直接双击“公交.bat”,不用管了,等着它完成吧!!
之后会得到一个“公交.txt”,大功告成了么?没有,因为公交数据的要求是unicode格式的,我们可以用iconv这个程序来在批处理中完成,但是,对于单个的文本,似乎没有必要引入一个命令了,用记事本轻松就搞定了!!
[ 本帖最后由 LanEast 于 2006-10-16 23:06 编辑 ] |
|