前言
上一篇文章爬取了爬虫练习平台的 spa 局部,有 Ajax 和接口加密,没有波及到登录,都是 GET 申请。
本篇文章持续爬前面的 login 局部,波及到了登录和验证。
环境筹备
思考到抓取数据字段的多样性,本次爬取将数据库换成了 Mongo,不须要每次都新建字段,放慢抓取速度。
新建一个名为 test 的数据库,其余环境和设置不变。
开始爬取
login1
login1 阐明如下:
模仿登录网站,登录时用户名和明码通过加密解决,适宜 JavaScript 逆向剖析。
是一个须要登录的网站,而且有加密,须要逆向 JS。我提前剖析了一下,因为登录之后并没有跳转到理论能够爬取的页面,所以就不写 scrapy 的代码了,只剖析登录申请所波及到的加密算法。
抓包剖析,老办法,关上控制台,打 xhr 断点,轻易输出账号密码,点击登录按钮,触发登录申请。
断在了老中央 send 办法上,顺着调用栈往上找,找找疑似在结构 data 的中央。
找到了一处,看起来是将 form 表单进行编码后传给 token,而后通过 post 函数发送进来。打断点,从新登录。
看起来是将轻易输出的账号密码传入到 c.encode
这个函数中进行编码之后就失去了 token 参数,单步跟进去看看。
遇到一些 get 办法间接步出就好,直到这里,是一个三元运算符,r 为 undefined
,所以会执行下边的 _encode(String(e))
办法,单步往下走。
到了这里,往上看,_encode
也是个三元运算符,还是好几个嵌套的那种。utob 函数只是进行了正则替换,真正的编码函数是 btoa 函数,单步进入。
调用了零碎的 btoa 函数,而 btoa 函数就是用来将字符串进行 base64 编码的。
所以,论断就是,账号密码被 base64 编码后赋值给了 token,而后提交给了服务器,代码如下:
data = { 'username': 'admin', 'password': 'admin' } url = 'https://login1.scrape.center/' # 留神这里是 get 申请,如果是 post 申请的话会报405错误代码。 r = requests.get(url=url, data={'token': base64.b64encode(json.dumps(data).encode())}) print(r.text) print(r.status_code)
代码中用的是 GET 申请(因为 GET 申请才能够胜利获取服务器响应),然而在网页上用的是 POST 申请,理论测试发现 POST 申请服务器会返回 405 谬误,猜想可能是后端服务没有指定申请形式的起因,不指定的话默认只接管 GET 申请。????????????
其实如果有点教训的人能够更快的解决问题,因为看到 token 这种大小写加数字的字符串基本上都会试一下 base64,如果不行的话再尝试逆向。
login2
login2 阐明如下:
对接 Session + Cookies 模仿登录,适宜用作 Session + Cookies 模仿登录练习。
无反爬,须要登录,网页构造和之前的没有区别。scrapy 中起始申请须要改成 POST 申请,并且须要通过表单提交账号密码数据,所以写法要略微扭转一下。残缺代码如下:
class SSR1(scrapy.Spider): name = 'login2' def start_requests(self): urls = ['https://login2.scrape.center/login'] for url in urls: # 增加须要提交的表单参数 yield scrapy.FormRequest(url=url, callback=self.parse, formdata={'username': 'admin', 'password': 'admin'}) def parse(self, response, **kwargs): for a in range(1, 11): yield Request(url=response.urljoin(f'/page/{a}'), callback=self.parse_page) def parse_page(self, response): result = response.xpath('//div[@class="el-card item m-t is-hover-shadow"]') for a in result: item = Login2ScrapyItem() item['title'] = a.xpath('.//h2[@class="m-b-sm"]/text()').get() item['fraction'] = a.xpath('.//p[@class="score m-t-md m-b-n-sm"]/text()').get().strip() item['country'] = a.xpath('.//div[@class="m-v-sm info"]/span[1]/text()').get() item['time'] = a.xpath('.//div[@class="m-v-sm info"]/span[3]/text()').get() item['date'] = a.xpath('.//div[@class="m-v-sm info"][2]/span/text()').get() url = a.xpath('.//a[@class="name"]/@href').get() yield Request(url=response.urljoin(url), callback=self.parse_person, meta={'item': item}) def parse_person(self, response): item = response.meta['item'] item['director'] = response.xpath( '//div[@class="directors el-row"]//p[@class="name text-center m-b-none m-t-xs"]/text()').get() yield item
略微扭转一下写法,在 start_requests 中进行登录动作,scrapy 会主动解决 cookie,所以在回调函数中进行失常的翻页爬取就能够了。前面的代码都是一样的,只是数据库更改成了 Mongo。
残缺代码详见https://github.com/libra146/learnscrapy/tree/login2
login3
login3 阐明如下:
对接 JWT 模仿登录形式,适宜用作 JWT 模仿登录练习。
用到了 JWT,关上控制台开始抓包。
一共三个 xhr 申请,一个登录申请,一个 301 跳转,一个获取数据的申请,先看登录申请。
同样的 form 表单提交账号密码,提交后服务器返回了一个超长的 token,看字符串构造(三段式,用两个 .
隔开)返回的是 JWT 没错了。
看下一个申请,JWT 被放在了申请头中,依照格局在中间件中批改申请头即可。
class Login3(scrapy.Spider): name = 'login3' jwt = '' offset = 0 limit = 18 url = f'https://login3.scrape.center/api/book/?limit={limit}&offset=%s' def start_requests(self): # 增加须要提交的表单参数 yield scrapy.FormRequest(url='https://login3.scrape.center/api/login', callback=self.parse, formdata={'username': 'admin', 'password': 'admin'}) def parse(self, response, **kwargs): result = response.json() # 从响应中获取到 jwt if 'token' in result: self.jwt = result['token'] # 从起始页面开始爬取 yield scrapy.Request(url=self.url % self.offset, callback=self.parse_page) def parse_page(self, response, **kwargs): result = response.json() print(response.url) for a in result['results']: item = Login3ScrapyItem() item['title'] = a['name'] item['author'] = '/'.join([c.strip() for c in (a['authors'] or [])]) yield Request(url=f'https://login3.scrape.center/api/book/{a["id"]}/', callback=self.parse3, meta={'item': item}) if int(result['count']) > self.offset: self.offset += self.limit yield Request(url=self.url % self.offset, callback=self.parse_page) def parse3(self, response): item = response.meta['item'] result = response.json() item['price'] = result['price'] or 0 item['time'] = result['published_at'] item['press'] = result['publisher'] item['page'] = result['page_number'] item['isbm'] = result['isbn'] yield item
和 login2 一样应用 FormRequest 提交表单,在回调函数中解析 JWT,保存起来,而后失常翻页爬取,别忘记计算偏移和更新偏移。
class Login3DownloaderMiddleware: # Not all methods need to be defined. If a method is not defined, # scrapy acts as if the downloader middleware does not modify the # passed objects. @classmethod def from_crawler(cls, crawler): # This method is used by Scrapy to create your spiders. s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): # Called for each request that goes through the downloader # middleware. # Must either: # - return None: continue processing this request # - or return a Response object # - or return a Request object # - or raise IgnoreRequest: process_exception() methods of # installed downloader middleware will be called # 因为第一个登录申请是不须要jwt的,这时候实际上也没有申请到jwt,所以判断一下 if spider.jwt: request.headers.update({'authorization': f'jwt {spider.jwt}'}) return None def process_response(self, request, response, spider): # Called with the response returned from the downloader. # Must either; # - return a Response object # - return a Request object # - or raise IgnoreRequest return response def process_exception(self, request, exception, spider): # Called when a download handler or a process_request() # (from other downloader middleware) raises an exception. # Must either: # - return None: continue processing this exception # - return a Response object: stops process_exception() chain # - return a Request object: stops process_exception() chain pass def spider_opened(self, spider): spider.logger.info('Spider opened: %s' % spider.name)
接下来的每个申请都须要在申请头中增加 JWT 参数,所以须要在下载中间件中为每个申请增加申请头,别忘了在设置中启用下载中间件。
# Enable or disable downloader middlewares # See https://docs.scrapy.org/en/latest/topics/downloader-middleware.html DOWNLOADER_MIDDLEWARES = { 'learnscrapy.middlewares.Login3DownloaderMiddleware': 543, }
设置好所有之后就能够失常爬取了。
残缺代码详见https://github.com/libra146/learnscrapy/tree/login3
总结
以上为三个须要登录的网站应用 scrapy 爬取的案例,通过案例能够学习到应用 scrapy 登录的办法和 scrapy 能够主动解决 cookie,并且能够通过中间件来批改每个申请的申请头,其中还有一个网站波及到了 JS 逆向的内容。
参考链接
https://docs.scrapy.org/en/latest/index.html