鸣蜩十四 发表于 2021-11-6 16:15

爬虫学习-Scrape Center闯关(spa系列1-3)

# 场景
上次写了ssr系列,它主要依靠服务器渲染代码,并且没有什么困难的地方,主要学习的是request请求中的技巧和html页面元素的定位抓取等爬虫基础技巧。这次记录的是spa系列的1-3个,因为这三个是一个电影网站其他只是细节不同,记录起来比较容易点,这个系列的代码数据都通过Ajax加载,页面动态渲染,主要学习的是通过接口获取json数据并处理以及js的逆向分析,难度提升了一大截,这个系列我的目标是爬取首页每个电影的标题,主题,评分以及电影详情页里面的电影剧情

# 技术
json数据的处理,js逆向分析

# 关卡
### spa1
电影数据网站,无反爬,数据通过 Ajax 加载,页面动态渲染,适合 Ajax 分析和动态页面渲染爬取。

这关主要学习的是通过接口抓取指定的json数据并处理
```
import json
import urllib3
import requests
import pandas as pd

urllib3.disable_warnings()#去除因为网页没有ssl证书出现的警告
url,title,theme,score,content = [],[],[],[],[]
headers ={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/87.0.4280.141 Safari/537.36'}
global url_list,title_list,theme_list,score_list,content_list
for i in range(0,10):
    the_url = "https://spa1.scrape.center/api/movie/?limit=10&offset="+str(i*10)
    index=requests.get(the_url,headers=headers,verify=False)
    dict_data = json.loads(index.text)    #将响应的内容转化为json
    for x in range(0,10):
      title.append(dict_data['results']['name']+dict_data['results']['alias'])
      theme.append(str(dict_data['results']['categories']).replace('[','').replace(']','').replace("'",''))
      score.append(dict_data['results']['score'])
for y in range(1,101):
    the_url="https://spa1.scrape.center/api/movie/"+str(y)+"/"
    response=requests.get(the_url,headers=headers,verify=False)
    dict_data = json.loads(response.text)
    content.append(dict_data['drama'])

bt = {
    '标题':title,
    '主题':theme,
    '评分':score,
    '剧情介绍':content
}
work = pd.DataFrame(bt)
work.to_csv('work.csv')

```

### spa2
电影数据网站,无反爬,数据通过 Ajax 加载,数据接口参数加密且有时间限制,适合动态页面渲染爬取或 JavaScript 逆向分析。
这关主要学习的是js的逆向分析
#### 分析网站主页token产生的js
通过打开网站主页面,发现接口多了一个token的值
![在这里插入图片描述](https://img-blog.csdnimg.cn/e5f75b0eeb164c088d2a7533e1c87549.png)
而且点开电影详情页面,接口不仅多了一个token值,而且在movie/后面多了一串字符串
![在这里插入图片描述](https://img-blog.csdnimg.cn/fab378af4dca4318a0f13372eecebc78.png)
先对网站主页的token进行分析,在浏览器调试工具中调试相关js代码,分析token的产生加密方法
因为是网络请求,并且url中包括token,所以通过url断点入手
下断点,刷新请求
![在这里插入图片描述](https://img-blog.csdnimg.cn/2d1f529e597244de8e9d0f13efb10137.png)
在send函数处断下
![在这里插入图片描述](https://img-blog.csdnimg.cn/9638957f535f497bb27d6419a45ff6a1.png)
在右侧的栈依次往下看,在onFetchData函数中发现了token的值,取消url断点,在token处设下断点,刷新请求
![在这里插入图片描述](https://img-blog.csdnimg.cn/91fe177db8774909b903577e5e416e97.png)
object()向后面传入了当前的url,单步进入后面的代码,进入新的代码块,这次可以看到 sha1 函数和 base64 函数,应该是加密函数所在位置了,进行单步调试
![在这里插入图片描述](https://img-blog.csdnimg.cn/37705cea07ea487d9e939732f4dfda61.png)


首先可以看到时间戳,然后从arguments中获取了当前的url
![在这里插入图片描述](https://img-blog.csdnimg.cn/b6defb5eeeb24cd98b4ed2698b201ad4.png)

然后进行循环,循环之后将当前url和时间戳放进了r中,
![在这里插入图片描述](https://img-blog.csdnimg.cn/3feb7843af26499db46bc068a0dfaeac.png)
第10页的网站页面是这样的
![在这里插入图片描述](https://img-blog.csdnimg.cn/19c13de20bf0458fa818b0683b53bc59.png)
我们可以发现除了时间戳因为时间变化之外,中间的数值也改变了,我们可以看出来中间的数值的规律是:
假设当前页面为n,数值的值为a
a=(n-1)*10


r的值放到SHA1加密函数中进行加密,出来的值为o
![在这里插入图片描述](https://img-blog.csdnimg.cn/4866f79584c04b8bae55c6bb9ed622e8.png)

然后将o的值和时间戳t放进base64进行加密,加密后的值为c

然后返回
![在这里插入图片描述](https://img-blog.csdnimg.cn/4508149bf78a49a2b45e23640cfd7604.png)

可以看到最后token的值就是我们刚才的c值
这样,token的加密方式就已经解出来了,步骤为:

o=sha1(url,(页面数-1)*10,时间戳)=>c=Base64(o,时间戳)=> token=c

转换为代码为:

SHA1加密
```
    t = int(time.time())   #获取10位整数的时间戳
    s1 = f'/api/movie,{i*10},{t}'
    o = hashlib.sha1(s1.encode('utf-8')).hexdigest()   #进行SHA1加密
```
Base64加密
```
str_str = str(f"{o},{t}")
    bytesStr = str_str.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr)   #最后的base64加密
    b64str = b64str.decode('utf-8')   # 将字节转换为str
```
b64str就是最后的token的值了,在这个过程中需要注意的是两次加密,共用的是同一个时间戳,并不是一次加密获取一个

#### 分析网站电影详情页的url中的加密


我们刚开始看到在电影详情页面中除了token之外还有一串加密的字符
![在这里插入图片描述](https://img-blog.csdnimg.cn/fab378af4dca4318a0f13372eecebc78.png)
我们接下来的目标就是分析token和这串字符的产生方式

##### url中字符串的加密分析
这串字符是固定的,尝试直接url断点,然后堆栈往下读,到了熟悉的onFetchData函数
![在这里插入图片描述](https://img-blog.csdnimg.cn/88f30c3b1cb14761a179aeee97eb905d.png)
设置断点,清除掉url断点,继续刷新请求单步调试
![在这里插入图片描述](https://img-blog.csdnimg.cn/8dcf49632d7d4644adb1e013f4f3ade4.png)

可以看到在上面的e变量已经url计算好了,e变量来源于o变量,调整断点,进行断点调试,但是在调试的过程中发现值来源于key参数,但是在调试中并没有找到key的计算来源,只能全局搜索key,看看key是怎么来的
一共9个匹配项,一点点调试可疑的,最后找到

![在这里插入图片描述](https://img-blog.csdnimg.cn/05ca49a4f7df467793c9661cdcb426c5.png)
设置断点,刷新,单步进入

![在这里插入图片描述](https://img-blog.csdnimg.cn/c8e6cda4a7064a45ae41adae6174bac9.png)
继续调试
![在这里插入图片描述](https://img-blog.csdnimg.cn/4321b2c7d0924faf8e86c05b26a3612c.png)
最后可以看到那串加密过的字符串,加密的方式是通过n这个固定的字符串变量+t,而t则表示的是第几个电影,然后经过base64加密
所以加密的方式为:
Base64(ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb+t)
##### url中token分析
而token和上面的基本一致,但是中间的一些细节可能不一样,在分析过程中,加密方式的大致流程也是先SHA1加密,然后再Base64加密,但是中间的参数却是不一样了
大致流程为:
o=sha1(url,0,时间戳)=>c=Base64(o,时间戳)=> token=c
但是url中在后面加入了刚才的字符串,中间的本来是页数,现在固定为0
![在这里插入图片描述](https://img-blog.csdnimg.cn/bb1a0c5caa5349218b4e09bbd1aa0c78.png)


![在这里插入图片描述](https://img-blog.csdnimg.cn/479f06873a8f4c049279b8dac5dc51b3.png)

这部分代码的加密和上面的类似,就不再单独写

最后,所有的加密方式以及对应的代码都已经分析好了

单线程
```
import base64
import json
import time
import urllib3
import requests
import pandas as pd
import hashlib

urllib3.disable_warnings()#去除因为网页没有ssl证书出现的警告
url,title,theme,score,content = [],[],[],[],[]
headers ={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/87.0.4280.141 Safari/537.36'}
global url_list,title_list,theme_list,score_list,content_list,t

def token1():
    t = int(time.time())# 获取10位整数的时间戳
    s1 = f'/api/movie,{i*10},{t}'
    o = hashlib.sha1(s1.encode('utf-8')).hexdigest()   #进行SHA1加密
    str_str = str(f"{o},{t}")
    bytesStr = str_str.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr)   #最后的base64加密
    b64str_all = b64str.decode('utf-8')   # 将字节转换为str
    return b64str_all

def token2():
    a = 'ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb' + str(y)
    bytesStr = a.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr).decode('utf-8')
    return b64str
def decode2(b64str):
    t = int(time.time())# 获取10位整数的时间戳
    s1 = f'/api/movie/{b64str},0,{t}'
    o = hashlib.sha1(s1.encode('utf-8')).hexdigest()# 进行SHA1加密
    str_str =(f"{o},{t}")
    bytesStr = str_str.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr)# 最后的base64加密
    b64str_all = b64str.decode('utf-8')# 将字节转换为str
    return b64str_all

for i in range(0,10):
    the_url = "https://spa2.scrape.center/api/movie/?limit=10&offset="+str(i*10)+"&token="+token1()
    index=requests.get(the_url,headers=headers,verify=False)
    dict_data = json.loads(index.text)    #将响应的内容转化为json
    for x in range(0,10):
      title.append(dict_data['results']['name']+dict_data['results']['alias'])
      theme.append(str(dict_data['results']['categories']).replace('[','').replace(']','').replace("'",''))
      score.append(dict_data['results']['score'])
    for y in range(i*10+1,i*10+11):
      the_url = "https://spa2.scrape.center/api/movie/" + token2() + '/?token=' + decode2(token2())
      response = requests.get(the_url, headers=headers, verify=False)
      dict_data_1 = json.loads(response.text)
      content.append(dict_data_1['drama'])


bt = {
    '标题':title,
    '主题':theme,
    '评分':score,
    '剧情介绍':content
}
work = pd.DataFrame(bt)
work.to_csv('work.csv')

```

多线程
```
import base64
import datetime
import hashlib
import threading
import time
import pandas as pd
import json
import requests
import urllib3

urllib3.disable_warnings()#去除因为网页没有ssl证书出现的警告
title,theme,score,content = [],[],[],[]
headers ={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/87.0.4280.141 Safari/537.36'}

def token1(i):
    t = int(time.time())# 获取10位整数的时间戳
    s1 = f'/api/movie,{i*10},{t}'
    o = hashlib.sha1(s1.encode('utf-8')).hexdigest()   #进行SHA1加密
    str_str = str(f"{o},{t}")
    bytesStr = str_str.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr)   #最后的base64加密
    b64str_all = b64str.decode('utf-8')   # 将字节转换为str
    return b64str_all

def token2(y):
    a = 'ef34#teuq0btua#(-57w1q5o5--j@98xygimlyfxs*-!i-0-mb' + str(y)
    bytesStr = a.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr).decode('utf-8')
    return b64str
def decode2(b64str):
    t = int(time.time())# 获取10位整数的时间戳
    s1 = f'/api/movie/{b64str},0,{t}'
    o = hashlib.sha1(s1.encode('utf-8')).hexdigest()# 进行SHA1加密
    str_str =(f"{o},{t}")
    bytesStr = str_str.encode(encoding='utf-8')
    b64str = base64.b64encode(bytesStr)# 最后的base64加密
    b64str_all = b64str.decode('utf-8')# 将字节转换为str
    return b64str_all

def index():
    for i in range(0, 10):
      the_url = "https://spa2.scrape.center/api/movie/?limit=10&offset=" + str(i * 10) + "&token=" + token1(i)
      index = requests.get(the_url, headers=headers, verify=False)
      dict_data = json.loads(index.text)# 将响应的内容转化为json
      for x in range(0, 10):
            title.append(dict_data['results']['name'] + dict_data['results']['alias'])
            theme.append(str(dict_data['results']['categories']).replace('[', '').replace(']', '').replace("'", ''))
            score.append(dict_data['results']['score'])
    return title,theme,score

def details():
    for y in range(1,101):
      the_url = "https://spa2.scrape.center/api/movie/" + token2(y) + '/?token=' + decode2(token2(y))
      response = requests.get(the_url, headers=headers, verify=False)
      dict_data_1 = json.loads(response.text)
      content.append(dict_data_1['drama'])
    return content



if __name__=='__main__':
    begin = datetime.datetime.now()
    t1 = threading.Thread(target=index)
    t2 = threading.Thread(target=details)
    t1.start()
    t2.start()
    t1.join()
    t2.join()
    bt = {
      '标题': title,
      '主题': theme,
      '评分': score,
      '剧情介绍': content
    }
    work = pd.DataFrame(bt)
    work.to_csv('work.csv')
    stop = datetime.datetime.now()
    print(stop-begin)# 40s
```

因为想尝试一下单线程与多线程的区别,所以两种都写了一种,但是最后结果有点让人脑袋疼,多线程最后运行的时间和单线程的差不多,而且多次测试中,有时候还会比单线程更慢

### spa3
电影数据网站,无反爬,数据通过 Ajax 加载,无页码翻页,下拉至底部刷新,适合 Ajax 分析和动态页面渲染爬取。

这关是没有页码,下拉直接刷新,通过分析我们可以看出来,这个是通过构造api连接,limit控制个数,offset控制从第几个开始,api直接提供了所有的东西,所以直接把第一关的拿过来稍微改改就可以使用
![在这里插入图片描述](https://img-blog.csdnimg.cn/2b93cc23b2e445c394290a0953e4eb7f.png)
![在这里插入图片描述](https://img-blog.csdnimg.cn/4dd9aeea1bdc468191b07cca17a004b7.png)

总代码
```
import json
import urllib3
import requests
import pandas as pd

urllib3.disable_warnings()#去除因为网页没有ssl证书出现的警告
title,theme,score,content = [],[],[],[]
headers ={'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
                         'Chrome/87.0.4280.141 Safari/537.36'}
global url_list,title_list,theme_list,score_list,content_list
for i in range(0,10):
    the_url = "https://spa3.scrape.center/api/movie/?limit=10&offset="+str(i*10)
    index=requests.get(the_url,headers=headers,verify=False)
    dict_data = json.loads(index.text)    #将响应的内容转化为json
    for x in range(0,10):
      title.append(dict_data['results']['name']+dict_data['results']['alias'])
      theme.append(str(dict_data['results']['categories']).replace('[','').replace(']','').replace("'",''))
      score.append(dict_data['results']['score'])
      content.append(dict_data['results']['drama'])
bt = {
    '标题':title,
    '主题':theme,
    '评分':score,
    '剧情介绍':content
}
work = pd.DataFrame(bt)
work.to_csv('work.csv')

```

麦子1995 发表于 2021-11-8 09:12

鸣蜩十四 发表于 2021-11-8 09:29

麦子1995 发表于 2021-11-8 09:12
大佬牛皮,请问你这案例是从哪里找的啊

https://scrape.center/这个爬虫练习网站

麦子1995 发表于 2021-11-8 10:07

yijiuxiaole 发表于 2021-11-8 18:53

学习一下
页: [1]
查看完整版本: 爬虫学习-Scrape Center闯关(spa系列1-3)