LeetCode Archiver(3): 登录

Cookie和Session 为了获取我们自己的提交记录,我们首先要进行登录的操作。但我们都知道HTTP是一种无状态的协议,它的每个请求都是独立的。无论是GET还是POST请求,都包含了处理当前这一条请求的所有信息,但它并不会涉及到状态的变化。因此,为了在无状态的HTTP协议上维护一个持久的状态,引入了Cookie和Session的概念,两者都是为了辨识用户相关信息而储存在内存或硬盘上的加密数据。 Cookie是由客户端浏览器维护的。客户端浏览器会在需要时把Cookie保存在内存中,当其再次向该域名相关的网站发出request时,浏览器会把url和Cookie一起作为request的一部分发送给服务器。服务器通过解析该Cookie来确认用户的状态,并对Cookie的内容作出相应的修改。一般来说,如果不设置过期时间,非持久Cookie会保存在内存中,浏览器关闭后就被删除了。 Session是由服务器维护的。当客户端第一次向服务器发出request后,服务器会为该客户端创建一个Session。当该客户端再次访问服务器时,服务器会根据该Session来获取相关信息。一般来说,服务器会为Seesion设置一个失效时间,当距离接收到客户端上一次发送request的时间超过这个失效时间后,服务器会主动删除Session。 两种方法都可以用来维护登录的状态。为了简便起见,本项目目前使用Session作为维护登录状态的方法。 获取数据 分析 首先我们进入登录页面,打开开发者工具,勾选Preserve log。为了知道在登录时浏览器向服务器提交了哪些数据,我们可以先输入一个错误的用户名和密码,便于抓包。 通过分析"login/“这条request,我们可以知道我们所需要的一些关键信息,例如headers中的user-agent和referer,表单数据(form data)中的csrfmiddlewaretoken,login和password。显然,user-agent和referer我们可以直接复制下来,login和password是我们填写的用户名和密码。还有一个很陌生的csrfmiddlewaretoken。这是CSRF的中间件token,CSRF是Cross-Site Request Forgery,相关知识可以查询跨站请求伪造的维基百科。那么现在我们就要分析这个token是从何而来。 获取csrfmiddlewaretoken 我们将刚才获取到的csrfmiddlewaretoken复制下来,在开发者工具中使用搜索功能,可以发现这个csrfmiddlewaretoken出现在了登录之前的一些request对应的response中。例如在刚才打开登录页面,发送GET请求时,response的headers的set-cookie中出现了"csrftoken=…“,而这里csrftoken的值与我们需要在登录表单中提交的值完全相同。因此,我们可以通过获取刚才的response中的Cookies来获取csrfmiddlewaretoken的值。 首先我们通过发送GET请求来分析一下Cookies的构成 login_url = "https://leetcode.com/accounts/login/" session = requests.session() result = session.get(login_url) print(result) print(type(result.cookies)) for cookie in result.cookies: print(type(cookie)) print(cookie) 得到的结果是 <Response [200]> 状态码200,表示请求成功 <class 'requests.cookies.RequestsCookieJar'> cookies的类型是CookieJar <class 'http.cookiejar.Cookie'> 第一条cookie的类型是Cookie <Cookie__cfduid=d3e02d4309b848f9369e21671fabbce571548041181 for .leetcode.com/> 第一条cookie的信息 <class 'http.cookiejar.Cookie'> 第二条cookie的类型是Cookie <Cookie csrftoken=13mQWE9tYN6g2IrlKY8oMLRc4VhVNoet4j328YdDapW2WC2nf93y5iCuzorovTDl for leetcode.com/> 第二条cookie的信息,也就是我们所需要的csrftoken 这样一来我们便获取到了在提交表单信息时所需要的csrfmiddlewaretoken,之后我们便可以开始着手写登录的相关代码了。顺便一提,在使用Django进行后端开发的时候自动生成的csrf token的键也叫csrfmiddlewaretoken,不知道LeetCode是不是用Django作为后端开发框架的。 实现 首先我们需要在爬虫开始运行之前获取登录信息,将Session作为类的成员变量保存下来,方便在获取submissions时使用。同时我们需要在与爬虫文件相同的目录下新建config.json,将自己的用户名和密码保存在该json文件里,这样就能顺利登陆了。 def start_requests(self): self.Login() # 登录 questionset_url = "https://leetcode.com/api/problems/all/" yield scrapy.Request(url=questionset_url, callback=self.ParseQuestionSet) def Login(self): login_url = "https://leetcode.com/accounts/login/" login_headers = { "user_agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/71.0.3578.98 Safari/537.36'", "referer": "https://leetcode.com/accounts/login/", # "content-type": "multipart/form-data; boundary=----WebKitFormBoundary70YlQBtroATwu9Jx" } self.session = requests.session() result = self.session.get(login_url) file = open('./config.json', 'r') info = json.load(file) data = {"login": info["username"], "password": inf["password"], "csrfmiddlewaretoken": self.session.cookies['csrftoken']} self.session.post(login_url, data=data,headers=login_headers) print("login info: " + str(result)) 注意如果在headers中填写了content-type的值,可能会产生一些奇怪的错误信息,并且后续不能正确地获取自己的submissions,只需要user_agent和refere的信息即可。 ...

January 11, 2019 · 1 min

LeetCode Archiver(2):获取题目信息

创建爬虫 在新建好项目后,用PyCharm或其他IDE打开该项目。进入该项目文件夹,使用genspider命令新建一个爬虫: cd scrapy_project scrapy genspider QuestionSetSpider leetcode.com 其中QuestionSetSpider是爬虫的名字,leetcode.com是我们打算爬取的网站的域名。 新建好爬虫之后可以看到在项目的spiders文件夹下新增了一个名为 QuestionSetSpider.py的文件,这就是我们刚才新建的爬虫文件。这个爬虫文件会自动生成以下代码 # -*- coding: utf-8 -*- import scrapy class QuestionSetSpider(scrapy.Spider): name = 'QuestionSetSpider' allowed_domains = ['leetcode.com'] start_urls = ['http://leetcode.com/'] def parse(self, response): pass QuestionSetSpider类继承自scrapy.Spider,也就是scrapy框架中所有爬虫的基类; self.name属性是该爬虫的名字,在该爬虫文件的外部可以通过这个属性获取当前爬虫; self.allowed_domains是当前爬虫文件可以访问的域名列表,如果在爬取页面时进入了一个该域名以外的url会抛出错误; self.start_urls是一个url列表,基类中定义了start_requests函数,它会遍历self.start_urls,并对每一个url调用scrapy.Request(url, dont_filter=True),为了实现爬取题目的需求,我们需要重写self.start_urls函数 获取题目详细信息 分析 LeetCode使用了GraphQL进行数据的查询和传输,大部分页面都是通过JS渲染生成的动态页面,所以无法直接从页面上获取标签,即使使用提供JavaScript渲染服务的库(例如Splash)也无法获取全部的数据,所以只能通过发送请求来获取数据。 为了爬取题目的详细信息,我们首先要从题目列表进入每个题目对应的链接。 首先打开leetcode的problem列表,按F12打开Chrome的开发者工具,进入Network标签栏,勾选上Preserve log,刷新该页面。 可以看到,网页向 https://leetcode.com/api/problems/all/ 发送了一个名为"all/“的GET类型的Request,这就是获取所有题目链接和相关信息的请求。如果此时已经安装了Toggle JavaScript插件,我们可以直接右键点击“Open in new tab”,查看该请求返回的Response。 更方便的方法是使用postman向服务器发送一个相同的Request,并将其保存下来,这样如果我们下次需要查看相应的Response的时候就不需要再使用开发者工具了。 返回的Response是一个json对象,其中的"stat_status_pairs"键所对应的值是所有包含题目信息的list,而列表中的[“stat”][“question__title_slug”]就是题目所在的页面。以Largest Perimeter Triangle为例,将其title_slug拼接到https://leetcode.com/problems/ 后,进入页面https://leetcode.com/problems/largest-perimeter-triangle/ 。同样地,打开开发者工具,刷新页面,可以看到服务器返回了很多项graphql的查询数据,通过查看Request Payload可以找到其中operationName为"questionData"的一项,这就是当前题目的详细信息。 将Payload复制粘贴到postman的Body中,在Headers中设置Content-Type为application/json,发送请求,可以看到返回的是一个json对象,包含了该题目所对应的所有信息。 接下来我们就可以对该题目的信息进行处理了。 实现 为了获取题目列表的json对象,我们需要先重写start_requests函数。 def start_requests(self): self.Login() # 用户登录,后续会用到 questionset_url = "https://leetcode.com/api/problems/all/" yield scrapy.Request(url=questionset_url, callback=self.ParseQuestionSet) Request是scrapy的一个类对象,功能类似于requests库中的get函数,可以让scrapy框架中的Downloader向url发送一个get请求,并将获取的response交给指定的爬虫文件中的回调函数进行相应的处理,其构造函数如下 ...

December 21, 2018 · 2 min

LeetCode Archiver(1):Scrapy框架和Requests库

简介 Scrapy官方文档对Scrapy的介绍如下: Scrapy是一个为了爬取网站数据,提取结构性数据而编写的应用框架。可以应用在包括数据挖掘,信息处理或存储历史数据等一系列的程序中。 其最初是为了页面抓取(更确切来说, 网络抓取)所设计的,也可以应用在获取API所返回的数据(例如 Amazon Associates Web Services )或者通用的网络爬虫。 简而言之,Scrapy是基于Twisted库开发的,封装了http请求、代理信息、数据存储等功能的Python爬虫框架。 组件和数据流 下图是Scrapy官方文档中的架构概览图: 图中绿色箭头表示数据流,其他均为组件。 Scrapy Engine(引擎) 引擎负责控制数据流在系统的组件中流动,并在相应动作发生时触发事件。 Scheduler(调度器) 调度器从引擎接收request并将其保存,以便在引擎请求时提供给引擎。 Downloader(下载器) 下载器负责下载页面数据,并将其提供给引擎,而后再由引擎提供给爬虫。 Spiders(爬虫) Spider是由用户编写的用于分析response并提取item或额外跟进url的类。一个Scrapy项目中可以有很多Spider,他们分别被用于爬取不同的页面和网站。 Item Pipeline(管道) Item Pipeline负责处理被爬虫提取出来的item。可以对其进行数据清洗,验证和持久化(例如存储到数据库中)。 Downloader middlewares(下载器中间件) 下载器中间件是在引擎及下载器之间的组件,用于处理下载器传递给引擎的response。更多内容请参考下载器中间件。 Spider middlewares(爬虫中间件) Spider中间件是在引擎及Spider之间的组件,用于处理爬虫的输入(response)和输出(items和requests)。更多内容请参考爬虫中间件。 Data flow(数据流) Scrapy中的数据流由引擎控制,其过程如下: 1.引擎打开一个网站,找到处理该网站的爬虫并向该爬虫请求要爬取的url。 2.引擎从爬虫中获取到要爬取的url并将其作为request发送给调度器。 3.引擎向调度器请求下一个要爬取的url。 4.调度器返回下一个要爬取的url给引擎,引擎将url通过下载器中间件发送给下载器。 5.下载器下载页面成功后,生成一个该页面的response对象,并将其通过下载器中间件发送给引擎。 6.引擎接收从下载器中间件发送过来的response,并将其通过爬虫中间件发送给爬虫处理。 7.爬虫处理response,并将爬取到的item及跟进的新的request发送给引擎。 8.引擎将爬虫返回的item发送给管道,将爬虫返回的新的request发送给调度器。 9.管道对item进行相应的处理。 10.重复第二步,直到调度器中没有更多的request,此时引擎关闭该网站。 安装 1.下载安装最新版的Python3 2.使用pip指令安装Scrapy pip3 install scrapy 创建项目 首先进入你的代码存储目录,在命令行中输入以下命令: scrapy startproject LeetCode_Crawler 注意项目名称是不能包含连字符 ‘-’ 的 新建成功后,可以看到在当前目录下新建了一个名为LeetCode_Crawler的Scrapy项目,进入该目录,其项目结构如下: scrapy.cfg #该项目的配置文件 scrapy_project #该项目的Python模块 __init__.py items.py #可自定义的item类文件 middlewares.py #中间件文件 pipelines.py #管道文件 settings.py #设置文件 __pycache__ spiders #爬虫文件夹,所有爬虫文件都应在该文件夹下 __init__.py __pycache__ 至此Scrapy项目的创建就完成了。 ...

December 4, 2018 · 1 min