爬虫基础篇之多途径抓取失信人名单

篇幅有限

完整内容及源码关注公众号:ReverseCode,发送

需求

爬虫基础篇之Scrapy抓取京东之后,我们对scrapy有了一定的掌握,接下来通过多渠道汇总对失信人信息抓取入库。

  1. 抓取百度失信人名单
  2. 抓取最高人民法院失信人名单
  3. 抓取国家企业信用公示系统失信人公告
  4. 把上面三个来源的失信人信息进行合并,去重

目标

百度

  • 搜索失信人名单

  • 抓取数据: 失信人名称, 失信人号码,法人(企业), 年龄(企业的年龄为0), 区域,失信内容, 公布日期, 公布执行单位, 创建日期, 更新日期

image-20210427202346052

企业信用信息公示系统

image-20210427202703732

实现

  • 把抓取的数据, 统一存储到同一个数据库的, 同一张表中.
  • 如何去重?
    • 对于个人: 根据失信人号码, 检查一下, 如果不存在才插入.
    • 对于企业/组织:
      • 失信人证件号, 有的是组织机构代码, 有的是信用号, 企业信用信息公示系统的失信人公告有的没有证件号, 所以无法进行准确判断.
      • 区域 和 企业名称进行检查, 如果有就重复了, 没有才插入.

百度

scrapy startproject dishonest 创建爬虫项目

数据模型

定义数据模型继承自scrapy.Item的数据模型DishonestItem

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class DishonestItem(scrapy.Item):
# define the fields for your item here like:
# 失信人名称
name = scrapy.Field()
# 失信人证件号
card_num = scrapy.Field()
# 失信人年龄, 企业年龄都是0
age = scrapy.Field()
# 区域
area = scrapy.Field()
# 法人(企业)
business_entity = scrapy.Field()
# 失信内容
content = scrapy.Field()
# 公布日期
publish_date = scrapy.Field()
# 公布/执行单位
publish_unit = scrapy.Field()
# 创建日期
create_date = scrapy.Field()
# 更新日期
update_date = scrapy.Field()

分析

通过翻页发起请求,在控制台的All请求下出现了https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6899&query=失信人名单&pn=0&ie=utf-8&oe=utf-8&format=json的目标地址

参数:

  • resource_id=6899: 资源id, 固定值
  • query=失信人名单: 查询内容, 固定值
  • pn=0: 数据起始号码
  • ie=utf-8&oe=utf-8: 指定数据的编码方式, 固定值
  • format=json: 数据格式, 固定值

我们可以根据第一次请求, 获取到总的数据条数, 生成所有页面的URL.

image-20210427203819189

demo

1
2
3
4
5
6
7
8
9
url = 'https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6899&query=失信人&pn=10&rn=10&ie=utf-8&oe=utf-8'
headers = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
'Referer': 'https://www.baidu.com/s?ie=UTF-8&wd=%E5%A4%B1%E4%BF%A1%E4%BA%BA'
}

response = requests.get(url, headers=headers)
print(response.status_code)
print(response.content.decode())

image-20210427204510667

爬虫实现

  1. 设置默认请求头, 在settings.py文件中
1
2
3
4
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_14_2) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36',
'Referer': 'https://www.baidu.com/s?ie=UTF-8&wd=%E5%A4%B1%E4%BF%A1%E4%BA%BA'
}
  1. scrapy genspider baidu baidu.com 创建爬虫
  2. 分页实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class BaiduSpider(scrapy.Spider):
name = 'baidu'
allowed_domains = ['baidu.com']
start_urls = ['https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6899&query=失信人&pn=0&rn=10&ie=utf-8&oe=utf-8']

def parse(self, response):
# 构建所有页面请求
# 把响应内容的json字符串, 转为字典
results = json.loads(response.text)
# 取出总数据条数
disp_num = jsonpath(results, '$..dispNum')[0]
# print(disp_num)
# URL模板
url_pattern = 'https://sp0.baidu.com/8aQDcjqpAAV3otqbppnN2DJv/api.php?resource_id=6899&query=失信人&pn={}&rn=10&ie=utf-8&oe=utf-8'
# 每隔10条数据, 构建一个请求
for pn in range(0, disp_num, 10):
# 构建URL
url = url_pattern.format(pn)
# 创建请求, 交给引擎
yield scrapy.Request(url, callback=self.parse_data)

def parse_data(self, response):
"""解析数据"""
# 响应数据
datas = json.loads(response.text)
results = jsonpath(datas, '$..result')[0]
# 遍历结果列表
for result in results:
item = DishonestItem()
# 失信人名称
item['name'] = result['iname']
# 失信人号码
item['card_num'] = result['cardNum']
# 失信人年龄
item['age'] = int(result['age'])
# 区域
item['area'] = result['areaName']
# 法人(企业)
item['business_entity'] = result['businessEntity']
# 失信内容
item['content'] = result['duty']
# 公布日期
item['publish_date'] = result['publishDate']
# 公布/执行单位
item['publish_unit'] = result['courtName']
# 创建日期
item['create_date'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 更新日期
item['update_date'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# print(item)
# 把数据交给引擎
yield item

数据存储

  1. 创建数据库表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
 -- 创建数据库
create database dishonest;
-- 如果表存在就删除
drop table if exists dishonest;
-- 创建表
create table dishonest(
dishonest_id INT NOT NULL AUTO_INCREMENT, -- id主键
age INT NOT NULL, -- 年龄, 自然人年龄都是>0的, 企业的年龄等于0
name VARCHAR(200) NOT NULL, -- 失信人名称
card_num VARCHAR(50) , -- 失信人号码
area VARCHAR(50) NOT NULL, -- 区域
content VARCHAR(2000) NOT NULL, -- 失信内容
business_entity VARCHAR(20), -- 企业法人
publish_unit VARCHAR(200), -- 发布单位
publish_date VARCHAR(20), -- 发布单位
create_date DATETIME, -- 创建日期
update_date DATETIME, -- 更新日期
PRIMARY KEY (dishonest_id)
);
  1. 在settings中配置数据库信息
1
2
3
4
5
6
7
8
9
10
11
# 配置MYSQL
# MYSQL的主机IP地址
MYSQL_HOST = '127.0.0.1'
# MYSQL端口号
MYSQL_PORT = 3306
# MYSQL用户名
MYSQL_USER = 'root'
# MYSQL密码
MYSQL_PASSWORD = ''
# MYSQL数据库名
MYSQL_DB = 'dishonest'
  1. 使用pipeline存储数据到mysql
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
class DishonestListPipeline(object):

def open_spider(self, spider):
# 创建数据链接
self.connect = pymysql.connect(host="127.0.0.1",user="root", password="123",
db="dishonest",port=3306)
# 获取执行SQL的cursor
self.cursor = self.connect.cursor()

def close_spider(self, spider):
# 释放游标
self.cursor.close()
# 释放链接
self.connect.close()

def process_item(self, item, spider):

if item['age'] == 0:
# 如果年龄 == 0 , 就是企业, 就根据公司名和区域进行查询
name = item['name']
area_name = item['area']
select_sql = "select count(1) from t_dishonest where name='{}' and area = '{}'".format(name, area)
else:
# 如果是个人根据证件号, 数据条数
select_sql = "select count(1) from t_dishonest where card_num='{}'".format(item['card_num'] )

# 根据证件号, 数据条数
select_sql = "select count(1) from dishonest where card_num='{}'".format(item['card_num'])
# 执行查询SQL
self.cursor.execute(select_sql)
# 获取查询结果
count = self.cursor.fetchone()[0]
# 如果查询的数量为0, 说明该人不存在, 不存在就插入
if count == 0:
# 获取当前的时间, 为插入数据库的时间
item['create_date'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
item['update_date'] = datetime.now().strftime('%Y-%m-%d %H:%M:%S')
# 把数据转换为键, 值的格式, 方便插入数据库
keys, values = zip(*dict(item).items())
# 插入数据库SQL
insert_sql = 'insert into dishonest ({}) values({})'.format(
','.join(keys),
','.join(['%s'] * len(values))
)
# 执行插入数据SQL
self.cursor.execute(insert_sql, values)
# 提交
self.connect.commit()
else:
spider.logger.info('{} 重复'.format(item))

return item




if __name__ == '__main__':
pipeline = DishonestListPipeline()
pipeline.open_spider('xx')
item = {
'card_num': '12345'
}
pipeline.process_item(item, '')

随机User-Agent反反爬

  1. 在settings.py中添加USER_AGENTS列表
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
USER_AGENTS = [
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; AcooBrowser; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0; Acoo Browser; SLCC1; .NET CLR 2.0.50727; Media Center PC 5.0; .NET CLR 3.0.04506)",
"Mozilla/4.0 (compatible; MSIE 7.0; AOL 9.5; AOLBuild 4337.35; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727)",
"Mozilla/5.0 (Windows; U; MSIE 9.0; Windows NT 9.0; en-US)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Win64; x64; Trident/5.0; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 2.0.50727; Media Center PC 6.0)",
"Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 6.0; Trident/4.0; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; .NET CLR 1.0.3705; .NET CLR 1.1.4322)",
"Mozilla/4.0 (compatible; MSIE 7.0b; Windows NT 5.2; .NET CLR 1.1.4322; .NET CLR 2.0.50727; InfoPath.2; .NET CLR 3.0.04506.30)",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN) AppleWebKit/523.15 (KHTML, like Gecko, Safari/419.3) Arora/0.3 (Change: 287 c9dfb30)",
"Mozilla/5.0 (X11; U; Linux; en-US) AppleWebKit/527+ (KHTML, like Gecko, Safari/419.3) Arora/0.6",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.2pre) Gecko/20070215 K-Ninja/2.1.1",
"Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-CN; rv:1.9) Gecko/20080705 Firefox/3.0 Kapiko/3.0",
"Mozilla/5.0 (X11; Linux i686; U;) Gecko/20070322 Kazehakase/0.4.5",
"Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.9.0.8) Gecko Fedora/1.9.0.8-1.fc10 Kazehakase/0.5.6",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.56 Safari/535.11",
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_3) AppleWebKit/535.20 (KHTML, like Gecko) Chrome/19.0.1036.7 Safari/535.20",
"Opera/9.80 (Macintosh; Intel Mac OS X 10.6.8; U; fr) Presto/2.9.168 Version/11.52",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.11 (KHTML, like Gecko) Chrome/20.0.1132.11 TaoBrowser/2.0 Safari/536.11",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.71 Safari/537.1 LBBROWSER",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E; LBBROWSER)",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/535.11 (KHTML, like Gecko) Chrome/17.0.963.84 Safari/535.11 LBBROWSER",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E; QQBrowser/7.0.3698.400)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; SV1; QQDownload 732; .NET4.0C; .NET4.0E; 360SE)",
"Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1; QQDownload 732; .NET4.0C; .NET4.0E)",
"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.1; WOW64; Trident/5.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; .NET4.0C; .NET4.0E)",
"Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/21.0.1180.89 Safari/537.1",
"Mozilla/5.0 (iPad; U; CPU OS 4_2_1 like Mac OS X; zh-cn) AppleWebKit/533.17.9 (KHTML, like Gecko) Version/5.0.2 Mobile/8C148 Safari/6533.18.5",
"Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:2.0b13pre) Gecko/20110307 Firefox/4.0b13pre",
"Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:16.0) Gecko/20100101 Firefox/16.0",
"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.64 Safari/537.11",
"Mozilla/5.0 (X11; U; Linux x86_64; zh-CN; rv:1.9.2.10) Gecko/20100922 Ubuntu/10.10 (maverick) Firefox/3.6.10"
]
  1. 实现随机User-Agent下载器中间件
1
2
3
4
5
6
7
8
9
10
class RandomUserAgent(object):

def process_request(self, request, spider):
# 如果spider是公示系统爬虫, 就直接跳过
if isinstance(spider, GsxtSpider):
return None

# 3. 实现process_request方法, 设置随机的User-Agent
request.headers['User-Agent'] = random.choice(USER_AGENTS)
return None
  1. settings.py中开启中间件
1
2
3
4
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {
'dishonest.dishonest.middlewares.RandomUserAgent': 543,
}

代理IP反反爬

实现代理IP下载器中间件,在settings.py中开启, 并配置重试次数,继爬虫基础篇之IP代理池实现的动态IP代理池启动用于本次失信人抓取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ProxyMiddleware(object):

def process_request(self, request, spider):
# 实现process_request方法, 设置代理IP
# 如果spider是公示系统爬虫, 就直接跳过
if isinstance(spider, GsxtSpider):
return None

# 1. 获取协议头
protocol = request.url.split('://')[0]
# 2. 构建代理IP请求的URL
proxy_url = 'http://localhost:16888/random?protocol={}'.format(protocol)
# 3. 发送请求, 获取代理IP
response = requests.get(proxy_url)
# 4. 把代理IP设置给request.meta['proxy']
request.meta['proxy'] = response.content.decode()

return None

配置代理池中间件及重试次数(毕竟免费ip不稳定)

1
2
3
4
5
6
7
# 开启下载器中间件
DOWNLOADER_MIDDLEWARES = {
'dishonest.dishonest.middlewares.ProxyMiddleware': 500,
'dishonest.dishonest.middlewares.RandomUserAgent': 543,
}
# 配置重试次数, 当使用不稳定代理的时候,可能会导致请求失败
RETRY_TIMES = 6

![百度爬虫](爬虫基础篇之多途径抓取失信人名单/GIF 2021-4-27 22-15-36.gif)

国家企业信用公示系统

JS逆向之国家企业信用信息公示系统Cookie传递完成了cookie的逆向分析,本文利用Cookie的实现逻辑,在scrapy中实现公示系统的爬虫入库。

scrapy genspider gsxt gsxt.gov.cn 创建爬虫

准备起始URL, 打印响应内容

1
2
3
4
5
6
7
8
9
10
11
class GsxtSpider(scrapy.Spider):
name = 'gsxt'
allowed_domains = ['gsxt.gov.cn']
# 准备起始
start_urls = ['http://www.gsxt.gov.cn/corp-query-entprise-info-xxgg-100000.html']

def parse(self, response):
# 打印状态吗
print(response.status)
# 内容
print(response.text)

修改原来的随机User-Agent, 和随机代理的下载器中间件类, 如果是公示系统爬虫直接跳过.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 随机User-Agent下载器中间件
class RandomUserAgent(object):

def process_request(self, request, spider):

# 国家企业信用信息系统爬虫, 每次发送请求必须携带cookie信息
if isinstance(spider, GsxtSpider):
return
...

# 代理下载器中间件
class ProxyMiddleware(object):

def process_request(self, request, spider):
# 发送请求获取代理IP
# 如果是国家企业信用信息系统爬虫, 直接返回
if isinstance(spider, GsxtSpider):
return

定制cookie

为了实现代理IP, User-Agent, cookie信息生成, 绑定和重用,实现步骤如下:

  • 步骤:

    • 实现生成cookie的脚本
      • 用于生成多套代理IP, User-Agent, Cookie信息, 放到Redis
    • 实现公示系统中间件类,
      • 实现process_request方法, 从Redis中随机取出Cookie来使用, 关闭页面重定向.
      • 实现process_response方法, 如果响应码不是200 或 没有内容重试
      • 在setting.py文件件中配置, 开启该下载器中间
  • 实现生成cookie的脚本

    • 创建gen_gsxt_cookies.py文件, 在其中创建GenGsxtCookie的类
    • 实现一个方法, 用于把一套代理IP, User-Agent, Cookie绑定在一起的信息放到Redis的list中
      • 随机获取一个User-Agent
      • 随机获取一个代理IP
      • 获取request的session对象
      • 把User-Agent, 通过请求头, 设置给session对象
      • 把代理IP, 通过proxies, 设置给session对象
      • 使用session对象, 发送请求, 获取需要的cookie信息
      • 把代理IP, User-Agent, Cookie放到字典中, 序列化后, 存储到Redis的list中
    • 实现一个run方法, 用于开启多个异步来执行这个方法.
    • : 为了和下载器中间件交互方便, 需要在settings.py中配置一些常量.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
def push_cookie_to_redis(self):

while True:
try:
"""
2. 实现一个方法, 用于把一套代理IP, User-Agent, Cookie绑定在一起的信息放到Redis的list中
"""
# 1. 随机获取一个User-Agent
user_agent = random.choice(USER_AGENTS)
# 2. 随机获取一个代理IP
# response = requests.get('http://localhost:16888/random?protocol=http')
# proxy = response.content.decode()
# 3. 获取requests的session对象
session = requests.session()
# 4. 把User-Agent, 通过请求头, 设置给session对象
session.headers = {
'User-Agent': user_agent
}
# 5. 把代理IP, 通过proxies, 设置给session对象
# session.proxies = {
# 'http': proxy
# }
# 6. 使用session对象, 发送请求, 获取需要的cookie信息
index_url = 'http://www.gsxt.gov.cn/corp-query-entprise-info-xxgg-100000.html'
# 获取request的session对象, 可以自动合并cookie信息

# ######################################################使用session发送index_url请求###########################
response = session.get(index_url)
print(response.status_code)
# 第一次请求521 服务器借助这个请求设置一个Set-Cookie: __jsluid_h=8af7a39f7cdb1c46f8f624c972968c8f; max-age=31536000; path=/; HttpOnly到本地,并返回一段js
########################################################拿到第一个cookie########################
# 1. 提取script标签中的js
js1 = re.findall('<script>(.+?)</script>', response.content.decode())[0].replace('document.cookie=',
'').replace(
'location.href=location.pathname+location.search', '')
context = js2py.EvalJs()
###################################################根据第一个请求返回的js生成第二个cookie###############################
context.execute('cookies2 =' + js1)
cookies = context.cookies2.split(';')[0].split('=')
session.cookies.set(cookies[0], cookies[1]) # 到此拿到第两个cookie
######################################################拿到第二个cookie############################

# 第二次请求携带Cookie: __jsluid_h=6ed2648e0a734bc66e3011d648f6f1ab; __jsl_clearance=1619152879.013|-1|aS3lFknWlGtD%2FADiygf7vxl4yqk%3D返回一段js
# 添加jsdom实现浏览器上下文
js2 = '''const jsdom = require("jsdom");const {JSDOM} = jsdom;const dom = new JSDOM();window = dom.window;document = window.document;location = new Array();''' + \
re.findall('<script>(.+?)</script>', session.get(index_url).content.decode('utf-8'))[0]
# 正则获取document['cookie'],由于每次个数不一样我们取最后一个
cookies2_1 = re.findall(r"document\[.*?\]=(.*?)location", js2, re.S)[-1]
# 将document['cookie']内容返回给go函数
js3 = re.sub("};go", "return " + cookies2_1 + "};go", js2, 1)
# 获取调用go函数时里面的参数
request = re.findall(r"go\({(.*?)}\)", js3, re.S)[-1]
# 通过python修改js生成一个request方法
final_js = js3 + "\nfunction request() {return go({" + request + "})}"
# js调用request方法返回cookie并将新的__jsl_clearance塞给session中
cookies3 = execjs.compile(final_js).call('request').split(';')[0].split('=')
session.cookies.set(cookies3[0], cookies3[1])

# 第三次请求 修改了__jsl_clearance后服务端向客户端设置新cookie的SECTOKEN
session.get(index_url)
cookies = requests.utils.dict_from_cookiejar(session.cookies)
# print(cookies)
# 7. 把代理IP, User-Agent, Cookie放到字典中, 序列化后, 存储到Redis的list中
cookies_dict = {
COOKIES_KEY:cookies,
COOKIES_USER_AGENT_KEY:user_agent,
# COOKIES_PROXY_KEY:proxy
}
# 序列化后, 存储到Redis的list中
self.redis.lpush(REDIS_COOKIES_KEY, pickle.dumps(cookies_dict))
print(cookies_dict)
break
except Exception as ex:
print("error",ex)

settings.py中配置信息

1
2
3
4
5
6
# 定义cookie的键
COOKIE_KEY = 'COOKIE' # 字典中Cookie键
COOKIE_PROXY_KEY = 'COOKIE_PROXY' # 字典中代理IP的键
COOKIE_USER_AGENT_KEY = 'COOKIE_USER_AGENT' # 字典中User-Agent的键
REDIS_COOKIES_KEY = 'REDIS_COOKIES' # Redis的cookie列表的键
REDIS_URL = 'redis://127.0.0.1:6379/0' # Redis数据库的链接

定制中间件

  • 步骤
    • 实现process_request方法, 从Redis中随机取出Cookie来使用, 关闭页面重定向.
    • 实现process_response方法, 如果响应码不是200 或 没有内容重试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class GsxtCookieMiddleware(object):

def __init__(self):
"""建立Redis数据库连接"""
self.redis = StrictRedis.from_url(REDIS_URL)

def process_request(self, request, spider):
"""从Redis中随机取出Cookie来使用, 关闭页面重定向."""
count = self.redis.llen(REDIS_COOKIES_KEY)
random_index = random.randint(0, count-1)
cookie_data = self.redis.lindex(REDIS_COOKIES_KEY, random_index)
# 反序列化, 把二进制转换为字典
cookie_dict = pickle.loads(cookie_data)

# 把cookie信息设置request
request.headers['User-Agent'] = cookie_dict[COOKIES_USER_AGENT_KEY]
# 设置请求代理IP
request.meta['proxy'] = cookie_dict[COOKIES_PROXY_KEY]
# 设置cookie信息
request.cookies = cookie_dict[COOKIES_KEY]
# 设置不要重定向
request.meta['dont_redirect'] = True

def process_response(self, request, response, spider):
"""如果响应码不是200 或 没有内容重试"""
# print(response.status)
if response.status != 200 or response.body == b'':
# 备份请求
req = request.copy()
# 设置请求不过滤
req.dont_filter = True
# 把请求交给引擎
return req

return response

在setting.py文件件中配置中间件

1
2
3
4
5
DOWNLOADER_MIDDLEWARES = {
'dishonest.dishonest.middlewares.GsxtCookieMiddleware': 10,
'dishonest.dishonest.middlewares.ProxyMiddleware': 500,
'dishonest.dishonest.middlewares.RandomUserAgent': 543,
}

完善爬虫

  • 解析页面中的城市名称和id, 构建公告信息的URL
  • 解析失信企业公告信息
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
class GsxtSpider(scrapy.Spider):
name = 'gsxt'
allowed_domains = ['gsxt.gov.cn']
# 准备起始
start_urls = ['http://www.gsxt.gov.cn/corp-query-entprise-info-xxgg-100000.html']

custom_settings = {
'DOWNLOAD_DELAY' : 5
}

def parse(self, response):

# 获取城市标签的div列表
divs = response.xpath('//*[@id="qysx"]/div[3]/div')
# 遍历divs, 获取城市id和名称
for div in divs:
area_id = div.xpath('./@id').extract_first()
area_name = div.xpath('./label/text()').extract_first()
# 准备请求的URL
url = 'http://www.gsxt.gov.cn/affiche-query-area-info-paperall.html?' \
'noticeType=11&areaid=100000&noticeTitle=&regOrg={}'.format(area_id)
# 一个城市最多能够获取50条数据.
for i in range(0, 50, 10):
data = {
'start': str(i)
}
# 构建请求, 交给引擎
yield scrapy.FormRequest(url, formdata=data, callback=self.parse_data,
meta={'area_name': area_name})

def parse_data(self, response):
# print(response.text)
"""解析页面中的城市"""
area_name = response.meta['area_name']
result = json.loads(response.text)
datas = result['data']
for data in datas:
item = DishonestItem()
# 区域名称
item['area_name'] = area_name
# 公告标题
notice_title = data['noticeTitle']
name = re.findall('关?于?(.+?)的?列入.*', notice_title)[0]
item['name'] = name
# 由于抓取的是失信企业公告, 所以抓到都是企业; 年龄设置为0
item['age'] = 0
notice_content = data['noticeContent']
card_id = re.findall('经查.+[(\(]统一社会信用码/注册号:(\w+)[)\)]', notice_content)
item['card_num'] = card_id[0] if len(card_id) != 0 else ''
item['content'] = notice_content
# 公布单位
item['publish_unit'] = data['judAuth_CN']
# 获取到的时间, 是1970年1月1日 0时0分0秒 到发布时间的毫秒数
publish_ms = data['noticeDate']
# 转换为日期类型
publish_date = datetime.fromtimestamp(publish_ms / 1000)
item['publish_date'] = publish_date.strftime('%Y年%m月%d日')
yield item
文章作者: J
文章链接: http://onejane.github.io/2021/04/27/爬虫基础篇之多途径抓取失信人名单/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 万物皆可逆向
支付宝打赏
微信打赏