【Python】scrapy学习之爬虫练习平台3

前言

上一篇文章爬取了爬虫练习平台的 spa 部分,有 Ajax 和接口加密,没有涉及到登录,都是 GET 请求。

本篇文章继续爬后面的 login 部分,涉及到了登录和验证。

环境准备

考虑到抓取数据字段的多样性,本次爬取将数据库换成了 Mongo,不需要每次都新建字段,加快抓取速度。

新建一个名为 test 的数据库,其他环境和设置不变。

开始爬取

login1

login1 说明如下:

是一个需要登录的网站,而且有加密,需要逆向 JS。我提前分析了一下,由于登录之后并没有跳转到实际可以爬取的页面,所以就不写 scrapy 的代码了,只分析登录请求所涉及到的加密算法。

抓包分析,老方法,打开控制台,打 xhr 断点,随便输入账号密码,点击登录按钮,触发登录请求。

【Python】scrapy学习之爬虫练习平台3

断在了老地方 send 方法上,顺着调用栈往上找,找找疑似在构造 data 的地方。

【Python】scrapy学习之爬虫练习平台3

找到了一处,看起来是将 form 表单进行编码后传给 token,然后通过 post 函数发送出去。打断点,重新登录。

【Python】scrapy学习之爬虫练习平台3

【Python】scrapy学习之爬虫练习平台3

看起来是将随便输入的账号密码传入到 c.encode 这个函数中进行编码之后就得到了 token 参数,单步跟进去看看。

【Python】scrapy学习之爬虫练习平台3

遇到一些 get 方法直接步出就好,直到这里,是一个三元运算符,r 为 undefined,所以会执行下边的 _encode(String(e)) 方法,单步往下走。

【Python】scrapy学习之爬虫练习平台3

到了这里,往上看,_encode 也是个三元运算符,还是好几个嵌套的那种。utob 函数只是进行了正则替换,真正的编码函数是 btoa 函数,单步进入。

【Python】scrapy学习之爬虫练习平台3

调用了系统的 btoa 函数,而 btoa 函数就是用来将字符串进行 base64 编码的。

所以,结论就是,账号密码被 base64 编码后赋值给了 token,然后提交给了服务器,代码如下:

python">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 说明如下:

无反爬,需要登录,网页结构和之前的没有区别。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,打开控制台开始抓包。

【Python】scrapy学习之爬虫练习平台3

一共三个 xhr 请求,一个登录请求,一个 301 跳转,一个获取数据的请求,先看登录请求。

【Python】scrapy学习之爬虫练习平台3

【Python】scrapy学习之爬虫练习平台3

同样的 form 表单提交账号密码,提交后服务器返回了一个超长的 token,看字符串结构(三段式,用两个 . 隔开)返回的是 JWT 没错了。

【Python】scrapy学习之爬虫练习平台3

看下一个请求,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

以上是 【Python】scrapy学习之爬虫练习平台3 的全部内容, 来源链接: utcz.com/a/88356.html

回到顶部