python程序作业_python学习——一个用Python写的小作业

上周三的时候去面试了一家有意思的公司,也没什么正式的面试,也就是和团队的boss聊了聊,因为是一些完全做过的东西,所以boss只提了一个需求,让我回去花点时间解决。因为确实是一个问题的不大的事情,所以就将它当做了一个小作业。需求是:搭建一个简单的原型系统,在自己的电脑上,在一天之内抓取10w条(url ,title)的数据对,存在自己本地的数据库里,然后写一个查询的页面。

如果有做过的前辈/同学/朋友,应该觉得这是一个简单的事情。因为也确实如此,即使没有做过这方面的东西,用python的话,也是很容易上手,慢慢可以搞定的。

以下的内容或存在用词用语不当的问题,由于本人的专业水平有限,对于造成的不适请见谅。

首先是写爬虫的部分,用python的urllib来做的话,轻而易举。http://www.voidcn.com/article/p-kuvdnlaz-bes.html,这一篇博文中,博主在“描述如何实现”的部分,写的很清晰易懂,基本上看了后,即使完全没概念的人也能大致理解网络爬虫是干嘛的。还有一篇 http://blog.chinaunix.net/uid-11538492-id-2869940.html,里面有一个有意思的设计,就是每个爬虫都有自己的最大抓取量,抓取到阈值时,爬虫线程就主动停下来,准备退出,虽然我在写自己的爬虫部分时没有实现这一点,但我仍然觉得这是一个有意思的设计。OK,回到这里的需求,抓取(url , title)的数据对。所以我需要获得10w份网页源码,解析获得url(s) 和title。最直观的想法是多线程,来提高处理效率。但怎样利用多线程好呢?一开始我在我的虚拟机里进行了简单的尝试,通过观察,我发现时间上的消耗主要发生在两个部分:抓取(网页源码)和读(网页源码),这两个步骤对应的操作是 urllib.urlopen(...) 和 read()。前者主要消耗网卡资源,后者更多的消耗CPU资源(这里我忽略了对内存资源的考虑) ,并且如果只跑这样一个程序,相对于有些时段缓慢的网速,CPU还是有不少闲置的。所以我决定将这两个操作分别写成两类线程中来做,而不是完全由一种线程来完成,这样基本可以保证urllib.urlopen(..)可以更高频的被调用,能对网卡资源有更多的利用。并且利用Queue.Queue,可以写出三个队列,分别用来存储url,response,和(url, title)。专门负责爬取的线程,将存储url的队列作为主要的数据输入源,通过从这个队列上获取url,然后调用urlopen,将获取的response添加到response队列上,然后再获取下一条url...;专门负责读response的线程,从response队列上,获取response,读取后,解析出需要的信息,分别添加到url队列上和(url,title)队列上。所以基于生产-消费者模型,爬虫部分整个在逻辑流程上呈现一种爬取线程,队列,读线程,队列组成的环。

以下的代码只作为一种基础的参考,其中仍存在一些问题。

import threading, urllib, Queue, socket, time

# the crawler just do crawl, they dont parse that

class Crawler(threading.Thread) :

def __init__(self,in_queue,out_queue,ban_queue,fin) :

threading.Thread.__init__(self)

self._in_queue = in_queue # the clear url queue

self._out_queue = out_queue # the (url_response, url) queue

self._ban_queue = ban_queue # the timed out url

self._fin = fin # fin is Event set by other thread

def run(self) :

while not self._fin.isSet() :

try :

url = self._in_queue.get()

if url in self._ban_queue.queue :

continue

resp = urllib.urlopen(url)

self._out_queue.put((resp,url))

self._in_queue.task_done()

except IOError :

if not url in self._ban_queue.queue :

self._ban_queue.put(url)

self._in_queue.task_done()

except Exception, e :

print 'Exception happened in Crawler...',e

break

self._in_queue.task_done()

def join(self):

threading.Thread.join(self)

# the parser just do parse the response get from urlopen

class Parser(threading.Thread) :

def __init__(self,in_queue,out_queue,data_queue,fin) :

threading.Thread.__init__(self)

self._in_queue = in_queue # the (url_response,url) queue

self._out_queue = out_queue # the clear url queue

self._data_queue = data_queue # the (url, title) queue

self._fin = fin

self._url_record = []

def run(self) :

while not self._fin.isSet() :

try :

resp, url = self._in_queue.get()

if url in self._url_record :

continue

self._url_record.append(url)

contant = resp.read()

title = contant.split('',1)[0].split('

',1)[-1]

urls = []

_urls = contant.split('href="')[1:]

for i in _urls :

_url = i.split('"')[0]

if _url.startswith('.ico') or _url.startswith('.css') or _url.__contains__('linkid') :

continue

elif _url.startswith('http') :

urls.append(_url)

for i in urls :

if i not in self._url_record :

self._out_queue.put(i)

self._data_queue.put((url,title,charset))

self._in_queue.task_done()

except Exception, e :

print 'Exception happened in parser...',e

if not self._fin.isSet() :

continue

else :

break

def join(self) :

threading.Thread.join(self)

def crawl() :

# init

socket.setdefaulttimeout(8)

url_list = Queue.Queue()

resp_list = Queue.Queue()

data_list = Queue.Queue()

ban_list = Queue.Queue()

finish = threading.Event()

crawler0 = Crawler(url_list,resp_list,ban_list,finish)

crawler1 = Crawler(url_list,resp_list,ban_list,finish)

crawler2 = Crawler(url_list,resp_list,ban_list,finish)

parser0 = Parser(resp_list,url_list,data_list,finish)

parser1 = Parser(resp_list,url_list,data_list,finish)

threads = [crawler0, crawler1, crawler2, parser0, parser1]

init_url = ['http://www.baidu.com','http://www.sina.com.cn','http://www.hao123.com','http://www.qq.com']

total_num = 100000

for i in init_url :

url_list.put(i)

# start threads

for i in threads :

i.start()

count = 0

while data_list.qsize() < total_num :

time.sleep(10)

count += 1

for i in threads :

if not i.isAlive() :

break # early terminate

print 'data(%d/%d) done...time(%d * 10s)' % (data_list.qsize(), total_num, count)

finish.set()

for i in threads :

i.join()

if __name__=='__main__' :

crawl()

可以添加一些打印消息,或者使用logging模块,将一些信息写到log文件里方便检查,不过作为基础,上面的代码应该能说明这个小作业的一种思路了。上面这部分代码,我在我的本(联想的Y470)上,从晚上0点开始,跑了约8h10min,利用学校夜间良好的网络状况,抓取了10w条数据。上面的代码存在这样一些问题:对获得response的解析的部分是值得怀疑的。我确实对前端编程不太了解,并且正则表达式的使用确实应该多加练习,所以我只好通过观察一些页面的网页源码,然后利用上面所示的分片的方法来做了,这部分实在需要怀疑。其次,就是这里的线程数了,Crawler和Parser的比例,我做的测试次数有限,所以无法判断怎样的一个C和P的比例是合适的。

之后就是数据入库了。我被推荐使用mongodb,为了方便,我安装并简单学习了pymongo。除了"$set", "$push"等等这些修改器我没打算进一步细究,pymongo模块真的是简单好用好上手,真心感谢这些了不起的贡献者。其他的就不说了,这里我遇到的主要问题有:首先是url中的'.',pymongo下document的键值对中key是不能含有点号的,即'.' ;其次是编码问题了,上面爬虫部分隐藏的另一个大问题就是没有获取charset,因此获取的title值存在编码问题,我对编码没有了解,但显然pymongo下document中的键值对是unicode编码的,并且当我写入utf-8编码的中文字符串时也没有什么问题,其他的编码问题,我还没有进行尝试,因为翻看的一些文章,已经让我对python2.x的编码问题感到头晕了(据说python3的编码问题得到了好转,不知道具体情况时怎么样的)。点号的问题很好解决,可以str.replace来解决,用类似“ /#/#/ “这样奇怪的字符组合来代替,我想第二次replace回去的时候发生误replace的概率应该比较小,并且在这里学习一下合法的url编码也是值得的 http://www.cnblogs.com/leaven/archive/2012/07/12/2588746.html。

最后就是写一个查询的web页面了。用web.py的话,即使没有经验也能感到很好上手,只是对于 templates/xxx.html,如果没有写过HTML方面的东西,还需要投入点时间。我没有这方面的经验,并且也耐性也差,所以当想到我可能需要动态的根据查询结果在返回页面上显示10条,或者20条记录时,我发现几个简单的例子上内容根本无法满足我的需求,于是我打算看看web.py的template.py的代码,希望能得到帮助。

按照 Class Render --> def __getattr__ --> _load_template --> return Template(...)的顺序,我发现web.py给出的使用模板的例子 render = web.template.render('templates/') ,其实是可以这样使用的:你完全可以打开一个.html文件,在文件流的适当的位置加入适当的html语句,然后将这个流作为参数,交由web.template.Template(类)来创建一个对象,然后在需要的地方,通过在这个对象后面加括号的方式调用它,这样你就在某种意义上,拥有了动态的页面,而不拘泥于编写.html文件了。这也是目前我在使用web.py的过程中发现的值得一说的有趣的东西。

以上,就是所谓的一个python小作业,希望能对和我一样没有前端经验的同学有所裨益。

  • 0
    点赞
  • 0
    收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
©️2022 CSDN 皮肤主题:数字20 设计师:CSDN官方博客 返回首页
评论
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值