基于Scrapy+MySQL爬取国家药监局100w+数据踩坑记录
基于Scrapy+MySQL爬取国家药监局100w+数据踩坑记录
- 1. 网页请求返回json数据的处理
- 2. Scrapy的Request中回调函数间的信息交流
- 3. MySQL报错:pymysql.err.InternalError: (1046, '')
- 3.1 打印一下sql语句,同时放入Navicat中执行。
- 3.2 检查程序连接mysql的设置是否正确,打印出配置信息检查:
- 4. pymysql报错:AttributeError: 'NoneType' object has no attribute 'encoding' using pymysql
- 5. Navicat中利用sql建表
- 5.1 基本信息表
- 5.2 详细信息表
- 6. IP被封问题
- 6.1 策略一:设置download_delay
- 6.2 策略二:禁止cookies
- 6.3 策略三:使用user-agent池
- 6.4 策略四:使用IP池
- 6.5 策略五:分布式爬取
- 7. 保存至MySQL数据库去重问题
- 8. 解决scrapy自动进行网页去重问题
- 8.1 解决办法一:在Request中加入参数:dont_filter = True
- 8.2 解决办法二:配置setting
- 9 利用字典避免多重if...elif..else提升效率
- 10. pymysql.err.DataError: (1406, '')
- 11. scrapy爬虫的暂停与重启
- 12 Todo
基于Scrapy+MySQL爬取国家药监局100w+数据踩坑记录
- 1. 网页请求返回json数据的处理
- 2. Scrapy的Request中回调函数间的信息交流
- 3. MySQL报错:pymysql.err.InternalError: (1046, '')
- 3.1 打印一下sql语句,同时放入Navicat中执行。
- 3.2 检查程序连接mysql的设置是否正确,打印出配置信息检查:
- 4. pymysql报错:AttributeError: 'NoneType' object has no attribute 'encoding' using pymysql
- 5. Navicat中利用sql建表
- 5.1 基本信息表
- 5.2 详细信息表
- 6. IP被封问题
- 6.1 策略一:设置download_delay
- 6.2 策略二:禁止cookies
- 6.3 策略三:使用user-agent池
- 6.4 策略四:使用IP池
- 6.5 策略五:分布式爬取
- 7. 保存至MySQL数据库去重问题
- 8. 解决scrapy自动进行网页去重问题
- 8.1 解决办法一:在Request中加入参数:dont_filter = True
- 8.2 解决办法二:配置setting
- 9 利用字典避免多重if...elif..else提升效率
- 10. pymysql.err.DataError: (1406, '')
- 11. scrapy爬虫的暂停与重启
- 12 Todo
1. 网页请求返回json数据的处理
Scrapy的返回对象Response没有json()方法,所以如果请求结果是json数据的话,必须用json.loads()方法处理成python可以处理的json对象。即
result = json.loads(response.text)
2. Scrapy的Request中回调函数间的信息交流
Scrapy中Request的回调函数间的信息传递,在scrapy 1.7之前的版本是通过meta参数传递实现,最新的scrapy 1.7开始推荐使用cb_kwargs进行传递,无法通过return返回。比如start_requests(self)方法中有:total_pages = yield Request(url, callback=self.get_total_pages),尽管在get_total_pages(self, response)方法中有return total pages, 但是返回到start_requests(self)方法中的total_pages是None,而不是get_total_pages()返回的total_pages。示例:
# 错误的方法def start_requests(self):"""发起初始请求:return: """url = 'http://mobile.cfda.gov.cn/datasearch/QueryList?tableId=36&searchF=Quick%20Search&pageIndex=1&pageSize=15'total_pages = yield Request(url, callback=self.get_total_pages)print(total_pages)def get_total_pages(self, response):"""获取目录页总页数:param url::return: total_pages"""# 要用json.loads()将字符串转换为json数据total_items = json.loads(response.text)[0]['COUNT'] # 获取总条数# 获取总页数,每页15条数据,所以页数=总条数/15,如果巧好整除则+1,否则+2if total_items/15 == int(total_items/15):total_pages = int(total_items/15) + 1else:total_pages = int(total_items/15) + 2return total_pages# 推荐的方法def start_requests(self):"""发起初始请求:return:"""# 1.数据库实例初始化self.mysql = MysqlClient(self.settings)table_id = 25page_index = 1logger.info('开始发起请求...')content_url = self.content_base_url.format(table_id=table_id, page_index=page_index)# Request跟进我们的URL,并将response作为参数传递给self.get_total_pages,典型的回调函数yield Request(content_url, callback=self.get_total_pages, cb_kwargs=dict(table_id=table_id))def get_total_pages(self, response, table_id):"""获取目录页总页数:param url::return: total_pages"""# 要用json.loads()将字符串转换为json数据total_items = json.loads(response.text)[0]['COUNT'] # 获取总条数# print(total_items)# 获取总页数,每页15条数据,所以页数=总条数/15,如果巧好整除则+1,否则+2if total_items/15 == int(total_items/15):total_pages = int(total_items/15) + 1else:total_pages = int(total_items/15) + 2# table_id = response.meta['table_id'] # 这是原来meta的用法,现在已经不推荐这么传参数count = 0for page_index in range(1, total_pages):count += 1content_url = self.content_base_url.format(table_id=table_id, page_index=page_index)if count > 50:count = 0# time.sleep(random.uniform(1, 2)) # 请求延迟# 请求产品的目录页yield Request(url=content_url, callback=self.parse_content, cb_kwargs=dict(table_id=table_id))
3. MySQL报错:pymysql.err.InternalError: (1046, ‘’)
这个问题花了很多时间,总结一下解决的思路和流程。
3.1 打印一下sql语句,同时放入Navicat中执行。
打印sql语句的方法:
print('开始执行sql语句')
sql = self.cursor.mogrify(sql)
print(sql)
如果Navicat中执行成功,则说明sql语句没有问题,问题出在连接数据库上面。转入步骤2。
如果Navicat中执行失败,说明sql语句有问题,检查表的名字、字段名是否意义对应,以及其中是否包含特殊字符,有特殊字符的表名或者字段名需要用”``“包裹,比如有“/”。中文的建议用”`字段名`“或者"`表名`".
3.2 检查程序连接mysql的设置是否正确,打印出配置信息检查:
print('开始连接MySQL数据库')
print('打印主机号:{}'.format(self.host))
print(self.user)
print(self.password)
print(self.db)
print(self.charset)
print(self.port)
4. pymysql报错:AttributeError: ‘NoneType’ object has no attribute ‘encoding’ using pymysql
原因是:charset编码问题,pymysql世界里,“utf8”是一个有效的字符集,但“utf-8”不是。具体参考:https://stackoverflow.com/questions/49828342/attributeerror-nonetype-object-has-no-attribute-encoding-using-pymysql
self.conn = pymysql.connect(host=self.host, user=self.user, password=self.password, db=self.db,port=self.port,charset=self.charset)
# self.charset不能是'utf-8',可以是'utf8'
5. Navicat中利用sql建表
一个非常好用的测试sql语法的网站:sql测试
5.1 基本信息表
这个表通用,只要把表名替换一下就可以了。
-- 进口药品基本表
DROP TABLE IF EXISTS `jkyp_content_info`;
CREATE TABLE `jkyp_content_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`product_count` INT(11) DEFAULT NULL,`product_info` VARCHAR(255) DEFAULT NULL,`product_id` INT(11) DEFAULT NULL,`url` VARCHAR(255) DEFAULT NULL,`crawl_time` VARCHAR(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
5.2 详细信息表
详细表每个都需要单独建一个表和scrapy.Field()字段比较麻烦。
不过这里我写了个小脚本帮助来偷点懒。
s = """批准文号
产品名称
...
原批准文号
药品本位码
药品本位码备注
注
"""s = s.split('\n') # 转换成列表
ls_1 = list()
ls_2 = list()
# 建表用
for each in s[:-1]:ls_1.append('`' + each + '`' + ' VARCHAR(255) DEFAULT NULL,')
for each in ls_1:print(each)print('='*100)
# 建scrapy.Field()用
ls_2.append('product_id = scrapy.Field()')
for each in s[:-1]:ls_2.append(each + ' = scrapy.Field()')
ls_2.extend(['url = scrapy.Field()', 'crawl_time = scrapy.Field()'])
for each in ls_2:print(each)
屏幕会自动输出符合格式的scrapy.Field()和建表部分,直接复制粘贴完善一些就好。省事很多。
-- 国产药品详细信息
DROP TABLE IF EXISTS `gcyp_detail_info`;
CREATE TABLE `gcyp_detail_info` (`id` int(11) NOT NULL AUTO_INCREMENT,`product_id` INT(11) DEFAULT NULL,`批准文号` VARCHAR(255) DEFAULT NULL,`产品名称` VARCHAR(255) DEFAULT NULL,`英文名称` VARCHAR(255) DEFAULT NULL,`商品名` VARCHAR(255) DEFAULT NULL,`剂型` VARCHAR(255) DEFAULT NULL,`规格` VARCHAR(255) DEFAULT NULL,`上市许可持有人` VARCHAR(255) DEFAULT NULL,`生产单位` VARCHAR(255) DEFAULT NULL,`生产地址` VARCHAR(255) DEFAULT NULL,`产品类别` VARCHAR(255) DEFAULT NULL,`批准日期` VARCHAR(255) DEFAULT NULL,`原批准文号` VARCHAR(255) DEFAULT NULL,`药品本位码` VARCHAR(255) DEFAULT NULL,`药品本位码备注` VARCHAR(255) DEFAULT NULL,`注` VARCHAR(255) DEFAULT NULL,`url` VARCHAR(255) DEFAULT NULL,`crawl_time` VARCHAR(255) DEFAULT NULL,PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
6. IP被封问题
参考文献:Scrapy研究探索(七)——如何防止被ban之策略大集合
6.1 策略一:设置download_delay
download_delay的作用主要是设置下载的等待时间,大规模集中的访问对服务器的影响最大,相当与短时间中增大服务器负载。
下载等待时间长,不能满足段时间大规模抓取的要求,太短则大大增加了被ban的几率。
使用注意:
download_delay可以设置在settings.py中,也可以在spider中设置。
6.2 策略二:禁止cookies
所谓cookies,是指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密),禁止cookies也就防止了可能使用cookies识别爬虫轨迹的网站得逞。
使用:
在settings.py中设置COOKIES_ENABLES=False。也就是不启用cookies middleware,不想web server发送cookies。
6.3 策略三:使用user-agent池
所谓的user-agent,是指包含浏览器信息、操作系统信息等的一个字符串,也称之为一种特殊的网络协议。服务器通过它判断当前访问对象是浏览器、邮件客户端还是网络爬虫。在request.headers可以查看user agent。
scrapy本身是使用Scrapy/版本号 来表明自己身份的。这也就暴露了自己是爬虫的信息
6.4 策略四:使用IP池
web server应对爬虫的策略之一就是直接将你的IP或者是整个IP段都封掉禁止访问,这时候,当IP封掉后,转换到其他的IP继续访问即可。
可以使用Scrapy+Tor+polipo
配置方法与使用教程可参见:http://pkmishra.github.io/blog/2013/03/18/how-to-run-scrapy-with-TOR-and-multiple-browser-agents-part-1-mac/。
6.5 策略五:分布式爬取
这个,内容就更多了,针对scrapy,也有相关的针对分布式爬取的GitHub repo。可以搜一下。
7. 保存至MySQL数据库去重问题
使用sql语句,添加去重函数:
def _select_product_id(self, query_sql=''):"""去重函数,查找product_id字段,如果存在返回[(1,1)],不存在则会返回():param query_sql::return:"""self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)self.cursor.execute("set names utf8") # utf8 字符集self.cursor.execute(query_sql)# sql = self.cursor.mogrify(query_sql)# print(sql)# print(self.cursor.fetchall())return self.cursor.fetchall() # cursor.fetchall()只能调用一次,再次调用就返回()空元组def _insert(self, insert_sql=''):"""执行插入MySQL:param insert_sql::return:"""try:self.cursor = self.conn.cursor(pymysql.cursors.DictCursor)self.cursor.execute("set names utf8") # utf8 字符集# insert_sql = self.cursor.mogrify(insert_sql)# print(insert_sql)self.cursor.execute(insert_sql)self.conn.commit()return Trueexcept Exception as e:logger.error('err:s% : %s', insert_sql, str(e))self.conn.rollback()self.cursor.close()return Falsedef save_base_info(self, item):"""保存信息到MySQL数据库:param item::return:"""try:table = item.tableproduct_id = item['product_id']# keys = ', '.join(item.keys())keys = '`' + '`, `'.join(item.keys()) + '`' # 因为是item类名称有中文values = ', '.join(['%s'] * len(item))insert_sql = f'INSERT INTO `{table}`({keys}) VALUES ({values})'insert_sql = insert_sql % tuple([r"'" + pymysql.escape_string(value) + r"'" if isinstance(value, str) else valuefor value in item.values()])# query_sql = f'SELECT EXISTS (SELECT 1 FROM `{table}` WHERE product_id={product_id})'query_sql = f'SELECT 1 FROM `{table}` WHERE product_id={product_id}'if self._select_product_id(query_sql):logger.info(f'{product_id}已存在MySQL数据库中')return Trueelse:return self._insert(insert_sql)except Exception as e:logger.error('保存信息失败,item=%s, err=%s', item, str(e))return None
8. 解决scrapy自动进行网页去重问题
参考链接Scrapy - Filtered duplicate request
因为在获取目录页总页数时,访问过一次page_index=1的url,是scrapy有基于布隆过滤器对url进行去重,导致后面存储时,这个网址无法再次抓取。
8.1 解决办法一:在Request中加入参数:dont_filter = True
这种办法需要在每一个Request加入dont_filter=True参数。
yield Request(content_url, callback=self.get_total_pages, cb_kwargs=dict(table_id=table_id), dont_filter = True)
8.2 解决办法二:配置setting
可以一步到位,不用一个个去设置Request。
# settings.py
DUPEFILTER_CLASS = 'scrapy.dupefilters.BaseDupeFilter'
9 利用字典避免多重if…elif…else提升效率
因为涉及到很多个table_id,如果用if…elif…else…需要写的特别长,而且效率会受影响。如下:
if table_id == 36:item = JKYPDetailItem()elif table_id == 25:item = GCYPDetailItem()elif table_id == 26:item = GCQXDetailItem()elif table_id == 27:item = JKQXDetailItem()elif table_id == 68:item = GCTSYTHZPDetailItem()elif table_id == 69:item = JKTSYTHZPDetailItem()elif table_id == 122:item = ZYYSDetailItem()elif table_id == 96:item = WSYDDetailItem()else:logger.error('表格号有误,请确认输入表格号是否正确!')return Noneprint(item.table)
采用字典优化后
# 采用字典, 避免过多的if...elif...else,提升效率,简化代码detail_items = {36:JKYPDetailItem(), 25:GCYPDetailItem(), 26:GCQXDetailItem(), 27: JKQXDetailItem(),68: GCTSYTHZPDetailItem(), 69: JKTSYTHZPDetailItem(), 122: ZYYSDetailItem(),96: WSYDDetailItem()}item = detail_items.get(table_id)if not item:logger.error('表格号有误,请确认输入表格号是否正确!')
代码量减少了,而且执行也会变快。
10. pymysql.err.DataError: (1406, ‘’)
因为数据量大于了设置的数据类型。对于文本类型,超过了varchar(255),则需要设置为LONGTEXT类型, 把表的字段类型修改即可。
11. scrapy爬虫的暂停与重启
启动时变为:
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
然后按ctrl+C暂停,再使用启动即可,注意不同的爬虫,要使用不同的文件夹。
scrapy crawl somespider -s JOBDIR=crawls/somespider-1
12 Todo
- 日志的配置不够灵活,后期需要优化。
- 构建一个IP池,不再设置下载延迟,提升效率。。
后记:
我从本硕药学零基础转行计算机,自学路上,走过很多弯路,也庆幸自己喜欢记笔记,把知识点进行总结,帮助自己成功实现转行。
2020下半年进入职场,深感自己的不足,所以2021年给自己定了个计划,每日学一技,日积月累,厚积薄发。
如果你想和我一起交流学习,欢迎大家关注我的微信公众号每日学一技
,扫描下方二维码或者搜索每日学一技
关注。
这个公众号主要是分享和记录自己每日的技术学习,不定期整理子类分享,主要涉及 C – > Python – > Java,计算机基础知识,机器学习,职场技能等,简单说就是一句话,成长的见证!
基于Scrapy+MySQL爬取国家药监局100w+数据踩坑记录相关推荐
- 基于Scrapy框架爬取豆瓣《复联4》影评,并生成词云
基于Scrapy框架爬取豆瓣<复联4>影评,并生成词云 1. 介绍及开发环境 2. 爬虫实现 2.1 新建项目 2.2 构造请求 2.3 提取信息 2.4 数据存储 2.4 运行结果 3. ...
- Scrapy+MySQL爬取去哪儿网
Scrapy+MySQL爬取去哪儿旅游[超详细!!!] 基于Python语言,利用Scrapy框架爬取信息,并持久化存储在MySQL 文章目录 Scrapy+MySQL爬取去哪儿旅游[超详细!!!] ...
- scrapy获取a标签的连接_python爬虫——基于scrapy框架爬取网易新闻内容
python爬虫--基于scrapy框架爬取网易新闻内容 1.需求[前期准备] 2.分析及代码实现(1)获取五大板块详情页url(2)解析每个板块(3)解析每个模块里的标题中详情页信息 点击此处,获取 ...
- 19. python爬虫——基于scrapy框架爬取网易新闻内容
python爬虫--基于scrapy框架爬取网易新闻内容 1.需求 [前期准备] 2.分析及代码实现 (1)获取五大板块详情页url (2)解析每个板块 (3)解析每个模块里的标题中详情页信息 1.需 ...
- 爬虫案例之爬取国家药监局化妆品生产许可明细(爬取动态加载数据)
一.实验目的 爬取国家药监局(化妆品生产许可信息管理系统服务平台 (nmpa.gov.cn))化妆品生产明细(具体到每家企业的具体信息),当我们进入该网站首页时,发现其结构为每页15条的json类型数 ...
- 14. python爬虫——基于scrapy框架爬取糗事百科上的段子内容
python爬虫--基于scrapy框架爬取糗事百科上的段子内容 1.需求 2.分析及实现 3.实现效果 4.进行持久化存储 (1)基于终端指令 (2)基于管道 [前置知识]python爬虫--scr ...
- 爬虫项目实战一:基于Scrapy+MongDB爬取并存储糗事百科用户信息
爬虫项目实战一:基于Scrapy+MongDB爬取并存储糗事百科用户信息 一.前言 二.项目目标 三.项目的环境配置 四.项目实现 1. 创建QSBK项目 2. 实现Spider 1. 实现一页网页上 ...
- 基于Webmagic的爬取B站用户数据的爬虫
基于Webmagic的爬取B站用户数据的爬虫 github: https://github.com/Al-assad/Spider-bilibiliUser-active 数据示例样本:http:// ...
- scrapy框架爬取王者荣耀英雄数据
scrapy框架爬取王者荣耀英雄属性 爬虫工程 爬虫文件 import scrapy from theKingPro.items import ThekingproItemclass ThekingS ...
- BeautifulSoup+scrapy+MySQL爬取人人词典
完整代码 https://github.com/CarryHJR/91dict 需求 爬取人人词典 人人词典最好的地方就是根据单词可以搜到语境,而且有对应的mp3音频,凭直觉这个可能会有版权问题,所以 ...
最新文章
- 图片上加动图怎么弄_用PS把千张图片拼成心爱的人的样子,只需三步!
- 白白浪费了这满园春色
- 指针应用-----链表二
- 在服务器托管中对流量和带宽进行限制
- java model.put_深入理解Java内存模型(一)——基础
- 大话设计模式—适配器模式
- Maven 中的pom.xml文件
- MySQL保留关键字
- php 手机端播放器,用JS代码适配电脑端和手机端播放器代码
- Too many input arguments.
- mac nginx 安装及PHP配置
- 胡萝卜,是鸡蛋,还是咖啡豆
- LINUX模拟键盘F5的脚本(未试过)
- WAS中间件服务器简介
- Android常用高质量框架
- LVGL 8.2图片缩放及旋转
- DSPE-PEG1-COOH可与伯胺基反应的反应性磷脂PEG共轭物之一
- 贪心(优先队列) - New Year Snowmen - CodeForces - 140C
- 正负筛选(neo正向+HSV-tk负向)原理
- Java 开发验证码。随机产生一个四位数的验证码,每位数可能是数字、大写字母或小写字母。