1、单机顺序抓取
这里使用的是Spynner库进行单进程抓取,通常用于目标明确及抓取内容数量不是很大的情况。加之我们用的python3,安装过程也复杂,所以我们就不必在这部分花时间了。但在这里,我们做一个简单的介绍。
优点:Spynner基于pyqt库,pyqt封装了强大的webkit,具有执行JavaScript的能力,可以完全模拟一个浏览器的功能和行为。
import spynner #引入模块 browser=spynner.Browser() #生成一个“浏览器” browser.show() #显示“浏览器” browser.load('http://www.phei.com.cn') #加载网页 browser.wait(10) #等待10秒,为了使“浏览器”多停留一会,以便观察 browser.close() #关闭“浏览器”,彻底释放资源
2、requests
requests库,它是python的http库,可以完成绝大部分与http应用相关的工作。与spynner不同的是,requests是一种非界面化的库,不能模拟浏览器的全部行为,常用的两种方法为:get和post。
get方法在爬虫基础那一讲,我们已经知道了它的部分使用方法。我们来一起看一下代码:
import requests #形式一 resp1=requests.get('http://www.phei.com.cn/') #形式二 params_dict ={ 'key1':'value1', 'key2':'value2' } resp2=requests.get('http://httpbin.org/get',params=params_dict) print(resp2.json())
post方法,比get方法的形式更复杂一些。get方式提交的数据最多只能是1024字节,post是没有大小限制的。
还是先来看看代码:
import requests post_data ={ 'user':'uname', 'upass':'upassword' } resp3=requests.post('http://httpbin.org/get',data=post_data) print(resp3.json())
header和cookie
header可以将我们的get和post伪装成浏览器,因为在抓取时对方的可能会判断请求方是否是浏览器,如果为浏览器才响应,返回数据。cookie中常常包含浏览器信息和用户信息,某些网站就是通过cookie信息来判断用户的。
3、并发和并行
主要技术包括:多线程、多进程、携程gevent。下面我们就进入正题:
为了更好的运用技术,我们先解释以下概念
- 并发,在一个时间段内发生若干事件的情况。就像单核的CPU,多任务操作系统的各个任务是并发运行的,各个任务会以分时的方式在一段时间内分别占用CPU依次执行,如果在自己的时间段里没有完成,那么需等待下一次得到CPU的使用权才会继续。
- 并行,同一时刻发生若干时间的情况。就像多核的CPU,多个任务可能在多个核上同时运行。
- 同步,并发或并行发生的各个任务之间不是孤立独自运行的,一个任务的进行可能需要在获得另一个任务给出的结果之后。各任务的运行会彼此相互制约。
- 异步,并发或并行发生的各个任务之间彼此是独立运行的,不受各自的影响。
实验爬取计算机类的图书名和链接,单线程、多线程、多进程、协程的时间比较,代码如下:
import requests from bs4 import BeautifulSoup import threading #线程 import multiprocessing #进程 import gevent #协程 from gevent import monkey #协程中需导入的包 monkey.patch_all() #协程没有这两句,会变成依顺序抓取 import time def format_str(s): return s.replace('\n','').replace(' ','').replace('\t','') def get_urls_in_pages(from_page_num,to_page_num): urls=[] search_word='计算机' url_part_1='http://www.phei.com.cn/module/goods/searchkey.jsp?Page=' url_part_2='&Page=2&searchKey=' for i in range(from_page_num,to_page_num+1): urls.append(url_part_1+str(i)+url_part_2+search_word) all_herf_list=[] for url in urls: #print(url) resp=requests.get(url).text bs=BeautifulSoup(resp,'lxml') a_list=bs.find_all('a') needed_list=[] for a in a_list: if 'href' in a.attrs: href_val=a['href'] title=a.text if 'bookid' in href_val and 'shopcar0.jsp' not in href_val and title !='': if [title,href_val] not in needed_list: needed_list.append([format_str(title),format_str(href_val)]) all_herf_list+=needed_list all_herf_file=open(str(from_page_num)+'_'+str(to_page_num)+'_'+'all_hrefs.txt','w') for href in all_herf_list: all_herf_file.write('\t'.join(href)+'\n') all_herf_file.close() #print(from_page_num,to_page_num,len(all_herf_list)) def single_threads_test(): t1=time.time() get_urls_in_pages(1,40) t2=time.time() print('单线程使用时间:',t2-t1) def multiple_threads_test(): t1=time.time() page_range_lst=[(1,10),(11,20),(21,30),(31,40)] th_lst=[] for page_range in page_range_lst: th=threading.Thread(target=get_urls_in_pages,args=(page_range[0],page_range[1])) th_lst.append(th) for th in th_lst: th.start() for th in th_lst: th.join() t2=time.time() print('多线程使用时间:',t2-t1) def multiple_process_test(): t1=time.time() page_range_lst=[(1,10),(11,20),(21,30),(31,40)] pool=multiprocessing.Pool(processes=4) for page_range in page_range_lst: pool.apply_async(get_urls_in_pages,page_range[0],page_range[1]) pool.close() pool.join() t2=time.time() print('多进程使用时间:', t2 - t1) def gevent_test(): t1=time.time() page_range_lst = [(1, 10), (11, 20), (21, 30), (31, 40)] jobs=[] for page_range in page_range_lst: jobs.append(gevent.spawn(get_urls_in_pages,page_range[0],page_range[1])) gevent.joinall(jobs) t2=time.time() print('协程使用时间:', t2 - t1) if __name__=='__main__': single_threads_test() multiple_threads_test() multiple_process_test() gevent_test()
输出结果如下:
单线程使用时间: 280.38603711128235 多线程使用时间: 68.45191502571106 多进程使用时间: 1.3390767574310303 协程使用时间: 15.374879360198975
可以发现相比单线程而言,多线程执行方式的确能够提高抓取的效率。然而多线程的在网络良好的情况是远远比不上多进程的,因为多进程抓取的快慢主要取决于网络。协程,又叫做子例程,可以把它看作是一种轻量的线程,似乎比多线程的处理更高效。