本篇是一个案例让你入门爬虫的最后一篇,在本篇中将简单的带你实现图片的下载以及加快爬取效率,使用多线程爬虫。
1.下载房源图片
本次只做让你学会下载图片,所以柱子是简单地简介一下,仅下载二手房列表页的展示图。以后会出一片专门下载图片,亿级别的按名称分文件夹保存图片的项目(敬请期待!!!)。
我们要保存到时蓝色框框的图,要下载图片,肯定要获得图片的URL地址,然后请求这个URL地址就可以获取图片内容,最后实现保存图片。代码实现:
# coding=utf-8
'''
在上一篇spider函数中添加如下代码即可
'''
#解析出图片url
image_url = house.xpath('a/img/@data-original')[0]
#下载图片
img = requests.get(image_url,headers=headers)
with open('./Qfang_image/{}.jpg'.format(title),'wb') as f:
f.write(img.content)
代码比较简单,就是把二进制内容保存下来就行了,所以这次直接获取二进制内容,用了content而不是text。
2.实现简单多线程爬虫
多线程是提高爬取速度和提高爬虫效率的主要方法之一。接下来,介绍其中一种实现多线程的办法,实现多线程也是有很多种办法的,这里直接介绍一种我认为比较简单的。
多进程和多线程在大部分情况下都可以加快处理效率,缩短处理时间,但是会出现通信、数据共享和加锁问题等。为了降低使用的门槛,使操作更简单,这里直接使用Python的标准库multiprocessing模块,这个模块使别人很容易利用多线程和多进程处理任务。
说说关于多线程和多进程的选择吧。一般对于计算(CPU)密集型任务适合多进程,IO密集型任务适合多线程。在下面解释下这两个概念,所谓IO密集型任务就是类似网络交互、文件读写、网络爬虫等任务,这些任务不依赖CPU进行操作,因此可以通过使用多线程来大大提升爬虫程序的效率。
计算(CPU)密集型 | IO密集型任务 |
---|---|
计算密集型,顾名思义就是应用需要非常多的CPU计算资源,在多核CPU时代,我们要让每一个CPU核心都参与计算,将CPU的性能充分利用起来,这样才算是没有浪费服务器配置,如果在非常好的服务器配置上还运行着单线程程序那将是多么重大的浪费。 对于计算密集型的应用,完全是靠CPU的核数来工作,所以为了让它的优势完全发挥出来,避免过多的线程上下文切换,比较理想方案是:线程数= CPU核数+1 也可以设置成CPU核数*2,这还是要看JDK的使用版本,以及CPU配置(服务器的CPU有超线程)。对于JDK1.8来说,里面增加了一个并行计算,计算密集型的较理想线程数 = CPU内核线程数*2 | 对于IO密集型的应用,就很好理解了,我们现在做的开发大部分都是WEB应用,涉及到大量的网络传输,不仅如此,与数据库,与缓存间的交互也涉及到IO,一旦发生IO,线程就会处于等待状态,当IO结束,数据准备好后,线程才会继续执行。因此从这里可以发现,对于IO密集型的应用,我们可以多设置一些线程池中线程的数量,这样就能让在等待的这段时间内,线程可以去做其它事,提高并发处理效率。 那么这个线程池的数据量是不是可以随便设置呢?当然不是的,请一定要记得,线程上下文切换是有代价的。 目前总结了一套公式,对于IO密集型应用:线程数= CPU核心数/(1-阻塞系数) 这个阻塞系数一般为0.8~0.9之间,也可以取0.8或者0.9。套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整。 |
首先,了解一下线程实现的基本步骤,分四步走:
(1)从multiprocessing.dummy 导入线程池。
(2)创建一个线程池,完成对线程池的初始化创建工作。
(3)把任务交给线程池。
(4)调用join方法等待这个线程池结束工作。
下面使用多线程来实现Q房网二手房源爬虫。
第一步,导入线程池和其他库。给导入的线程池起一个别名为pl。
from multiprocessing.dummy import Pool as pl
import requests
from lxml import etree
import csv
import time
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36 SE 2.X MetaSr 1.0"
}
pre_url = 'https://shenzhen.qfang.com/sale/f'
第二步,定义下载函数。
def download(url):
html = requests.get(url,headers=headers)
return etree.HTML(html.text)
第三步,定义数据写入函数。
def data_writer(item):
with open('qfang_shenzhen_ershou.csv','a',encoding='utf-8',newline='') as f:
writer = csv.writer(f)
writer.writerow(item)
第四步,定义图片保存函数image_saver。
def image_saver(url,title):
# 下载图片
img = requests.get(url, headers=headers)
with open('./Qfang_image/{}.jpg'.format(title), 'wb') as f:
f.write(img.content)
第五步,定义爬取函数spider。
def spider(list_url):
#下载列表页
selector = download(list_url)
house_list = selector.xpath("//div[@id='cycleListings']/ul//li[@class='clearfix']")
#循环解析每套房源
for house in house_list:
title = house.xpath("/div[1]/p[1]/a/text()")[0]
apartment = house.xpath("/div[1]/p[2]/span[2]/text()")[0]
area = house.xpath("/div[1]/p[2]/span[4]/text()")[0]
decoration_type = house.xpath("/div[1]/p[2]/span[6]/text()")[0]
cenggao = house.xpath("/div[1]/p[2]/span[8]/text()")[0].strip()
orientation = house.xpath("/div[1]/p[2]/span[10]/text()")[0]
build_finishtime = house.xpath("/div[1]/p[2]/span[12]/text()")[0]
location = house.xpath("/div[1]/p[3]/span[2]/a/text()")[0]
total_price = house.xpath("//div[@class='show-price']")[0].strip()
#解析并构造详情页URL
house_url = ('http://shenzhen.qfang.com' + house.xpath("div[1]/p[1]/a/@href")[0])
#下载详情页
sel = download(house_url)
time.sleep(1)
house_years = sel.xpath("//div[@class='housing-info']/ul/li[2]/div/ul/li[3]/div/text()")[0]
mortgage_info = sel.xpath("//div[@class='housing-info']/ul/li[2]/div/ul/li[5]/div/text()")[0]
item = [title,apartment,area,decoration_type,cenggao,orientation,build_finishtime,location,total_price,house_years,mortgage_info]
data_writer(item)
print('正在爬取', title)
# 解析出图片url
image_url = house.xpath('a/img/@data-original')[0]
image_saver(image_url,title)
第六步,编写主程序,初始化线程池。
if __name__ == '__main__':
pool = pl(4) #初始化线程池
初始化线程池这个参数是根据计算机的CPU核心数编写的。注意,计算机CPU核心数可以通过CPU-Z查看,也可以通过任务管理器或则 在命令行界面输入wmic进入命令行系统管理执行脚本界面, 然后我们通过cpu get *可以查看cpu的具体情况。
用列表推导式生成要爬取的URL列表,总共99页。
house_url = [pre_url+str(x) for x in range(1,100)]
接着使用线程池的map方法对要爬取的页面执行spider函数,其中线程池的map方法跟Python的map使用基本相同。
pool.map(spider,house_url)
最后,关闭线程池并等待所有线程结束。
pool.close()
pool.join()
这样就完成了整个Q房网多线程的改写,使用多线程实现了Q房网深圳市二手房房源数据的爬取。