用tornado爬素材网站

用tornado爬素材网站

(第一篇技术笔记)
工作需要建立一个飞机图片和视频库,一方面,可以从开源数据集中抽取需要的数据,如Pascal VOC,CIFAR10,COCO和ILSVRC等都包含飞机类别图片,ILSVRC的VID任务包含大量视频片段,我总共从这些数据集中获得了1000(VOC)+3000(COCO)+1400(ILSVRC)=5400张图片以及171段视频;另一方面,可以通过爬虫工具从网上获取更多资源,搜狐优酷这类网站的资源比较杂乱,不符合我的需求,获取效率不高,最终锁定了国内外各种视频和图库的素材资源网站。

​ 开发针对单个资源网站的爬虫相对简单,也叫垂直爬虫,一般为了提高效率会采用多线程,或者直接利用工具包,如scrapy等。tornado是一个轻量级web后端框架,像django和flask一样,tornado包含异步网络库,利用协程(单线程),可以简单高效地实现高并发。

1、爬取百度图片的简单示例

​ 示例代码借鉴自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"]

2、关注请求头和响应头

​ 在向服务器端发送请求时,除了目标连接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

你可能感兴趣的:(用tornado爬素材网站)