(第一篇技术笔记)
工作需要建立一个飞机图片和视频库,一方面,可以从开源数据集中抽取需要的数据,如Pascal VOC,CIFAR10,COCO和ILSVRC等都包含飞机类别图片,ILSVRC的VID任务包含大量视频片段,我总共从这些数据集中获得了1000(VOC)+3000(COCO)+1400(ILSVRC)=5400张图片以及171段视频;另一方面,可以通过爬虫工具从网上获取更多资源,搜狐优酷这类网站的资源比较杂乱,不符合我的需求,获取效率不高,最终锁定了国内外各种视频和图库的素材资源网站。
开发针对单个资源网站的爬虫相对简单,也叫垂直爬虫,一般为了提高效率会采用多线程,或者直接利用工具包,如scrapy等。tornado是一个轻量级web后端框架,像django和flask一样,tornado包含异步网络库,利用协程(单线程),可以简单高效地实现高并发。
示例代码借鉴自tornado官网文档(这里)5s获取300张。改一改可以实现各种用途,爬图片、视频、小说、新闻、评论、商品等。
import os
import re
import time
from tornado import httpclient, gen, ioloop, queues
from datetime import timedelta
from urllib.parse import quote
path = "./imgs/"
if not os.path.exists(path):os.makedirs(path)
e_names = set(os.listdir(path))
keyword = "战斗机"
re_imgs = re.compile('"thumbURL":"(.*?)",')
@gen.coroutine
def main():
''' 生产消费者模型,通过队列完成数据通信 '''
start = time.time()
concurrency = 500 # 设置并发大小和队列大小
q = queues.Queue(maxsize=concurrency)
@gen.coroutine
def worker():
# 消费每个下载链接
while True:
src = yield q.get()
try:
filename = src.split("/")[-1]
if filename not in e_names:
response = yield httpclient.AsyncHTTPClient().fetch(src)
with open(path+filename, "wb") as file:
file.write(response.body)
file.flush()
e_names.add(filename)
except Exception as e:
print("PRO_CON:"+str(e))
q.task_done()
# 启动所有消费者worker,此时队列为空,所有worker阻塞在`src = yield q.get()`语句
for i in range(concurrency):
worker()
# 启动生产者,获取10页图片地址,如何获取url链接,在浏览器按F12
# 打开"网络"(Networks),在刷新图片时抓包就可以看到
for page_index in range(1,11):
url = "https://image.baidu.com/search/acjson?tn=resultjson_com&ipn=rj&ct=201326592\
&is=&fp=result&queryWord="+quote(keyword)+"&cl=2&lm=-1&ie=utf-8&oe=utf-8&adpicid=\
&st=-1&z=&ic=0&word="+quote(keyword)+"&s=&se=&tab=&width=&height=&face=0&istype=2\
&qc=&nc=1&fr=&pn=%d"%(30*page_index)+"&rn=30&gsm=1e&1533995815287="
response = yield httpclient.AsyncHTTPClient().fetch(url)
srcs = re_imgs.findall(response.body.decode("utf8",errors="ignore"))
print("page: %03d, #pics: %d"%(page_index,len(srcs)))
for src in srcs:
yield q.put(src)
yield q.join(timeout=timedelta(seconds=300))
print("Concurrent task done in %d seconds"%(time.time()-start))
if __name__ == '__main__':
io_loop = ioloop.IOLoop.current()
io_loop.run_sync(main)
顺便说下,在数据解析性能方面,能用正则表达式和xpath,就不要用beautifulSoup和json(某些时候),解析过程一般如下:
import requests
from lxml import etree
import json
html = requests.get(some_url)
# re的使用
re_obj = re.compile('
')
srcs = re_obj.findall(html.text)
# xpath使用
selector=etree.HTML(html.text)
srcs = selector.xpath("//li/a/img/@src") #返回图片地址列表列表
# json使用
data = json.loads(html.text)
srcs = data["imgs"]
在向服务器端发送请求时,除了目标连接url之外,通常还需要合理设置请求头,cookie字段通常被服务器用于识别用户,保持通信状态,User-Agent表明你使用的浏览器及版本,响应头则是服务器返回给用户的数据包的一部分,会给出服务器的部分信息,如服务器软件类型(apache/tomcat),返回数据的类型(xhr/mp4/mov),服务器时间,以及set-cookie等,其中set-cookie字段告知浏览器在下次发送请求时需要设置cookie的内容,比如给你分配一个用户id,下次请求服务器时带上这个就知道你是谁了,另外响应头中的location字段会将请求重定位到新的url上,这也是很多资源网站保护数据内容的一种手段。
这是tornado关于header和request相关API的介绍,有个网站在下载视频时必须保持较长时间的连接,在header中可以将request_timeout设长一点,如果要执行发送数据的post请求,需要将data进行urlencode后,放入request的body字段中。
from urllib.parse import urlencode
from tornado import httpclient,httputil,ioloop
async def f():
data = urlencode({"wd":"战斗机"})
headers = httputil.HTTPHeaders()
headers.add("cookie","x123456789")
request = httpclient.HTTPRequest("http://www.baidu.com/s?"+data,
method="GET", headers=headers, body=None,
connect_timeout=1*60, request_timeout=10*60)
http_client = httpclient.AsyncHTTPClient()
try:
response = await http_client.fetch(request)
except Exception as e:
print("Error: %s" % e)
else:
# 查看响应头,content-type字段表明body中的文件类型
print(resopnse.headers)
# 查看传回的数据,body是字节码,可以是文本文件,也可以使音乐视频等内容
print(response.body.decode(errors="ignore"))
io_loop = ioloop.IOLoop.current()
io_loop.run_sync(f)
实践源码可以参考:https://github.com/ziweipolaris/simple-crawler