Python WebbingGrap 探索一

    前端时间自学了python的基本语法,为深入了解python,就打算真正的鼓捣些东西,加深记忆。据说,python经常用来网页抓取(爬虫),故而新手小试,并记之。

网页数据采集多线程设计:

1>输入:多个线程共享一个任务队列,如果该任务方案支持网络,可设计为分布式集群采集;2>输出:最简单的做法是直接将采集数据放入数据库,但频繁操作数据库会增加耗时,故而多个输出可共享一个消息队列,再用线程处理消息队列;事实上该消息队列可模拟为OS经典生产者和消费者模式中的共享缓冲池。

1.网例一之:python多线程数据采集

要点1:python多线程

python的多线程编程,这里题外话的说一下线程和进程的区别:1>进程(有时被称为重量级进程)是程序的一次执行每个进程都有自己的地址空间,内存,数据栈以及其他记录其运行轨迹的辅助数据(os层面)。进程可通过fork和spawn操作来完成其他任务,但由于各自内存独立,故只能使用进程间通讯(IPC),而不能直接共享信息;2>线程(有时被称为轻量级进程)与进程相似,不同是所有的线程运行在同一个进程中,共享相同的运行环境(包括数据空间,故可方便通讯)。线程有开始、顺序执行和结束三部分,持有自己的指令指针,记录自己运行到什么地方。线程的运行可能被中断(抢占)或睡眠(暂时被挂起),以让步于其他线程。线程一般都是并发执行的,事实上在单CPU系统中真正的并发是不可能的,每个线程会被安排成每次只占用CPU运行一段时间。多个线程共同访问同一片数据有可能导致数据结果的不一致的问题,这叫做竞态条件。

至于Python的多线程,由于其原始解释器CPython中存在着GIL(Global Interpreter Lock,全局解释器锁),虽然CPython线程库直接封装了系统的原生线程,但(整体进程)同一时间只会获得一个GIL线程执行,直到解释器遇到I/O操作或者操作次数达到一定数目时才会释放GIL。故即使多线程也只是做分时切换而已。

所以如果你的Python代码是CPU密集型,此时多线程的代码很有可能是线性执行的,这种情况下多线程是鸡肋,效率可能还不如单线程因;但如果你的代码是IO密集型,Python多线程可以明显提高效率。

但如果确实需要在CPU密集型的代码里用并行,可以用multiprocessing库。这个库是基于multi process实现了类multi thread的API接口,并且用pickle部分地实现了变量共享。

如果确实理不清Python代码是CPU密集型还是IO密集型,那么分别尝试下面的两种Python多线程方法:

1>from multiprocessing import Pool(这就是基于multithread实现了multiprocessing的API,多进程同步);

2>from multiprocessing.dummy import Pool(这是多线程实现的同步)两种方式都跑一下,哪个速度快用哪个就行了。

一般使用:

threading模块中的Thread类可用来创建线程,创建方法有如下两种:1.通过继承Thread类,重写它的run方法;2.创建一个threading.Thread对象,在初始化函数(__init__)中将可调用对象作为参数传入:threading.Thread.__init__(self,name=threadname)。

要点2. 目标数据及相关第三方packages

采集目标:淘宝; 采集数据:某一关键词领域的淘宝店铺名称、URL地址、店铺等级; 

相关第三方packages:requests(http://devcharm.com/pages/11-python-modules-you-should-know);beautifulsoup4(PS:保证lxml model已安装过);Redis

代码部分:

1. search_config.py


#coding=utf-8

class config:

keyword ='青岛'    search_type ='shop'   url='http://s.taobao.com/search?q='+ keyword  +'&commend=all&search_type='+ search_type +'&sourceId=tb.index&initiative_id=tbindexz_20131207&app=shopsearch&s='

simple_scrapy.py

#coding=utf-8

import request

from bs4 import BeautifulSoup

from search_config import config

from Queue import Queue

import threading

class Scrapy(threading.Thread)

def __init__(self,threadname,queue,out_queue):

    threading.Thread.__init__(self,threadname)

    self.sharedata=queue

    self.out_queue=out_queue

    self.threadname=threadname

    print threadname+'start......'

def run(self):

    url=config.url+self.sharesata.get()

    response=requests.get(url)

    self.out_queue.put(response)

    print self.threadname+'end......'

class Parse(threading.Thread):

def __init__(self,threadname,queue,out_queue):

threading.Thread.__int__(self,name=threadname)

self.sharedata=queue

self.out_queue=out_queue

self.threadname=threadname

print threadname+'start......'

def run(self)

response=self.sharedata.get()

body=response.content

soup=BeautifulSoup(body)

ul_html=soup.find('ul',{'id':'list-container'})

lists=ul_html.findAll('li',{'class':'list-item'})

stores=[]

for list in lists

store={}

try:

info=list.findAll('a',{'trace':'shop'})

 for info in infos

    attrs=dict(info.attrs)

    if attrs.has_key('class'):

       if 'rank' in attrs['class']

           rank_string=attrs['class']

          rank_num=rank_string[-2:]

          if rank_num[0]=='-':

            store['rank']=rank_num[-1]

          else:

            store['rank']=rank_num

       if attrs.has_key('title')

          store['title']=attrs['title']

          store['href']=attrs['href']

    except  AttibuteError:

      pass

if store:

   stores.append(store)

for store in stores: 

    print store['title'] +''+ store['rank']

print self.threadname +'end......'

def  main():

    queue=Queue()

    targets=Queue()

    stores=Queue()

    scrapy=[]

    for i  in range(0,13,6):#queue 原始请求#targets 等待解析的内容#stores解析完成的内容,这里为了简单直观,直接在线程中输出了内容,并没有使用该队列

        queue.put(str(i))

        scrapy= Scrapy('scrapy', queue, targets)

        scrapy.start()

        parse= Parse('parse', targets, stores)

        parse.start()

if__name__=='__main__':

   main()

注解:1》使用BeautifulSoup库,可以直接按照class或者id等html的attr来进行提取,相较于直接写正则表达式难度系数降低很多,当然执行效率上,相应的也就大打折扣了。

2》通过scrapy线程不断提供数据,parse线程从队列中取出数据进行相应解析;作者将这二者写到了同一个循环体中:即get一个response后随即刻parse掉,但我认为这二者完全可以参照生产者消费者模式,将二者模块分开,仅共享一消息队列即可。

3》由于共享数据不存在安全问题,所以上面的例子都是非线程安全的,并没有为共享数据加锁,只是实现了最简单的FIFO,所以也不会是因为锁的开销导致效率没有得到真正提高

4》由于数据解析是CPU密集型操作,而网络请求是I/O密集型操作,考虑上文说到的GIL,数据解析操作操作的多线程并不能带来效率上的提升,相反可能因为线程的频繁切换,导致效率下降;而网络抓取的多线程则可以利用IO阻塞等待时的空闲时间执行其他线程,提升效率。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 我们前面提到了进程是由若干线程组成的,一个进程至少有一个线程。多线程优点: 在一个进程中的多线程和主线程分享相同的...
    第八共同体阅读 529评论 0 0
  • 1.进程和线程 队列:1、进程之间的通信: q = multiprocessing.Queue()2、...
    一只写程序的猿阅读 1,119评论 0 17
  • Python的面向对象 类 Class 类变量 Class variable 数据成员 Data member 函...
    JasonJe阅读 1,147评论 0 3
  • 线程 1.同步概念 1.多线程开发可能遇到的问题 同步不是一起的意思,是协同步调 假设两个线程t1和t2都要对nu...
    TENG书阅读 624评论 0 1
  • 线程 引言&动机 考虑一下这个场景,我们有10000条数据需要处理,处理每条数据需要花费1秒,但读取数据只需要0....
    不浪漫的浪漫_ea03阅读 374评论 0 0