
<<< 项目源码传送门>>>
首先这里给各位大佬拜个早年!
模块准备
这里推荐使用Anaconda集成环境,自带各类常用第三方模块,更高效率开发。 首先我们通过如下命令安装所需第三方模块
pip install tornado pyquery requests
目标Robots协议解析
这里着重强调下对robots.txt解析的重要性,以免造成一些不必要的麻烦,如果协议指出所要爬取的目录静止爬行,就最好不要碰! 我们可以看到这里并没有禁止抓取./photo/
目录,那就放心爬吧!
User-agent: * Disallow: /view/2015-01/ Disallow: /discovery/2015-06/ Disallow: /net/2015-02/ Disallow: /original/2015-09/ Disallow: /ztyw/2011-11/ Disallow: /view/xinwen/2015-08-10/ Disallow: /news/2015-06/ Disallow: /original/2015-07/ Disallow: /original/2015-10/ Disallow: /technew/
服务端实例效果图
(原效果是会动的!截的静态图)

逻辑思路
- 获取http://tech.huanqiu.com/photo/响应体,检索带有
神奇世界看看看
的元素标签,通过pyquery解析并返回对应TaskList - 迭代爬行TaskList每项的目标网页,并取到各个图片的链接及其标题,打包成json保存到本地(图片本身有点大,不推荐存储)
- 建立服务端,自定义显示模板,载入本地数据供移动端浏览
好了废话不多说,切入正题讲实现方法
初始化
首先我们初始化该爬虫,savepic
参数定义了爬行是否存取源图片,初始化所使用的爬虫对象,爬虫session、日志对象及当前任务列表,并自动生成本地数据存取目录。
ROBOTS_FILE = "http://tech.huanqiu.com/robots.txt" TargetUrl = "http://tech.huanqiu.com/photo/" TargetUrl_path = '/photo/' Local_path = "./img" Dely = 1 * 24 * 3600 class bot(object): def __init__(self, savepic=False): super(bot, self).__init__() self.savepic = savepic self.crawler = Anticrawler() self.session = self.crawler.session self.logger = logging.getLogger(type(self).__name__) self.task = None def start(self): self.pathinit(Local_path) self.checkaccess() self.GetTask() self.ReadTask() def pathinit(self, path): path = ABpath(path) if not os.path.exists(path): self.logger.info("Path not exists") os.mkdir(path) self.logger.info("Path init success") def resp_check(self, resp): if resp.status_code is 200 and resp.content: return resp self.crawler.random_user_agent()
robots.txt检测
检测爬行目录是否可被抓取
def checkaccess(self): try: resp = self.session.get(ROBOTS_FILE) if self.resp_check(resp) and TargetUrl_path not in [uri.split(" ")[0] for uri in resp.text.split("\n") if "Disallow" in uri]: return True else: exit("PATH NOT ALLOWED") except Exception as e: self.logger.critical(e) exit()
获取任务列表
在通过chrome自带网页调试工具查看源码后,我们取到了某一期《神奇世界看看看》的css选择器参数 body > div.box > div > div.fallsFlow > ul > li:nth-child(5) > h3 > a
。
由于网页中每一张栏目卡片都为一个li元素,所以要取到所有的卡片在语意上就是取到body > div.box > div > div.fallsFlow > ul
下所有 li > a
中的title
及href
数据,并根据href解析出每期id
值,如果title
中包含了关键字神奇世界看看看
那就是我们所想要的数据。 
def pkgtaskitem(self, data): keys = ["title", "href", "id"] title = jq(data).attr("title") href = jq(data).attr("href") ids = href.split("/")[-1].split(".")[0] return dict(zip(keys, [title, href, ids])) def GetTask(self): try: resp = self.session.get(TargetUrl) if self.resp_check(resp): content = jq(resp.content) self.task = [self.pkgtaskitem(items) for items in jq(resp.content)( "body > div.box > div > div.fallsFlow > ul").items('li>a') if "神奇世界看看看" in jq(items).attr("title")] self.logger.info(f"Get Task Success --> {self.task}") except Exception as e: self.logger.error(e)
获取每期数据
在这里我们可以看到,该响应体中直接包含了我们所需要的数据,于是利用pyquery直接提取出。 
def GetTaskItem(self, url): try: resp = self.session.get(url) if self.resp_check(resp): res = jq(resp.content)(".m_r > script").text().split( '"img" :')[1].split("};")[0] res = eval(res.replace(" ", "")) self.logger.info(f"Get Item Success --> {url} {len(res)}") return res except Exception as e: self.logger.error(e) def ReadTask(self): if self.task: for item in self.task: path = f"{Local_path}/{item['id']}" res = self.GetTaskItem(item['href']) item['datas'] = res self.SaveJSON(path, item) self.SavePic(path, res)
数据存储
在获取到每期的数据后,根据id值初始化目录并存取json以备使用。原图按需存取,由于大多数为GIF,不建议存取。
def SaveJSON(self, path, item): self.pathinit(path) with open(ABpath(f"{path}/data.json"), "w") as f: f.write(json.dumps(item, indent=4)) self.logger.info('JSON Data Saved') def SavePic(self, path, res): if platform.system() is not "Lunix" and self.savepic: for target in res: picpath = ABpath(f"{path}/{target['img_url'].split('/')[-1]}") if not os.path.exists(picpath): resp = self.session.get(target["img_url"]) if self.resp_check(resp): with open(picpath, "wb") as pic: pic.write(resp.content)
运行效果图
可以看到这里取到了若干期的数据并覆写入本地 
服务器端处理
模板采用ionic框架作为介质。 这里的Main
类通过 os.walk
方法遍历img/
下级目录文件夹名称列表,并通过max()
函数取得最大id值,而后重定向至Item
类并结合对应数据配合模板生成最新一期的页面。
import tornado.ioloop import tornado.web import tornado.netutil import tornado.httpserver import tornado.escape import os SERVER_PORT = 80 STATIC_PATH = os.path.join(os.path.dirname(__file__), 'img') class Item(tornado.web.RequestHandler): def get(self, doc_id): datapath = f"{STATIC_PATH}/{doc_id}/data.json" if os.path.exists(datapath): try: with open(datapath, "r") as f: self.render( "index.html", data=tornado.escape.json_decode(f.read())) except Exception as e: raise else: self.set_status(404) class Main(tornado.web.RequestHandler): def get(self): idlist = list(os.walk("./img"))[0][1] if idlist: self.redirect(f"/{max(idlist)}") def make_app(): settings = { "autoreload": True, "gzip": True, "static_path": STATIC_PATH } return tornado.web.Application([ (r"/([0-9]+)", Item), (r"/", Main) ], **settings) if __name__ == '__main__': sockets = tornado.netutil.bind_sockets(SERVER_PORT) app = tornado.httpserver.HTTPServer(make_app()) app.add_sockets(sockets) tornado.ioloop.IOLoop.current().start()