本文共 9270 字,大约阅读时间需要 30 分钟。
标题中的英文首字母大写比较规范,但在python实际使用中均为小写。
2018年7月20日笔记 Scrapy官方文档网址: 网页在chrome浏览器打开,经过谷歌翻译,如下图所示:IDE(Intergrated development Environment),集成开发环境为jupyter notebook和Pycharm
操作系统Win10 语言及其版本:python3.6使用Selector初始化方法实例化对象赋值给response变量。
css和extract这2个方法的使用示例如下:.//和//的区别如下图所示,一般来说要使用.//
打开cmd或者powershell在其中输入并运行命令,运行结果如下图所示:
新建爬虫工程命令:scrapy startproject BoleSave导入工程的按钮位置如下图所示:
选中工程文件夹,然后点击OK,如下图所示:
网页持久化只需要编辑爬虫文件就可以,下面是save.py文件的代码。
第21行dirName变量的值可以设置网页文件保存的位置,例如:dirName = "d:/saveWebPage"将网页文件保存在D盘的saveWebPage文件夹中。 可以根据个人情况进行修改,不建议将其设置为工程所在文件夹,因为可能导致Pycharm卡顿。# -*- coding: utf-8 -*-import scrapyimport osimport redef reFind(pattern,sourceStr,nth=1): if len(re.findall(pattern,sourceStr)) >= nth: return re.findall(pattern,sourceStr)[nth-1] else: return 1class SaveSpider(scrapy.Spider): name = 'save' allowed_domains = ['blog.jobbole.com'] start_urls = [] url_before = "http://blog.jobbole.com/all-posts/page/{}/" for i in range(1,560): start_urls.append(url_before.format(i)) def parse(self, response): dirName = "d:/saveWebPage" if not os.path.isdir(dirName): os.mkdir(dirName) url = response.url page_id = int(reFind("\d+", url)) html = response.text fileName = "%s/%03d.html" % (dirName, page_id) with open(fileName, 'w', encoding="utf-8") as file: file.write(html) print("目录页面第%d页被存放到%s目录中的%03d.html文件中" % (page_id,dirName,page_id))
运行命令:scrapy crawl save,此命令运行时cmd进入的目录必须在爬虫工程内
运行结果如下图所示:已经将网站上的网页保存为本地html文件,并将559个文件打包为压缩文件。
压缩文件下载链接: 密码: qtp3 解析后的数据存到mysql数据库中,需要先创建数据库bole 采用了数据库连接池,异步多线程操作数据库可以提高效率。创建爬虫工程命令:scrapy startproject BoleParse
进入爬虫工程目录:cd ./BoleParse/ 创建爬虫文件命令: scrapy genspider parse blog.jobbole.comimport scrapyfrom scrapy import Fieldclass BolearticleItem(scrapy.Item): id = Field() title = Field() publishTime = Field() category = Field() digest = Field() detailUrl = Field() imgUrl = Field()
import scrapyfrom ..items import BoleparseItemimport reimport osdef reFind(pattern,sourceStr,nth=1): if len(re.findall(pattern,sourceStr)) >= nth: return re.findall(pattern,sourceStr)[nth-1] else: return 1class ParseSpider(scrapy.Spider): name = 'parse' start_urls = [] baseUrl = "file:///%s/saveWebPage/%03d.html" for i in range(560): start_urls.append(baseUrl %(os.getcwd(),i)) def parse(self, response): def find(xpath, pNode=response): if len(pNode.xpath(xpath)): return pNode.xpath(xpath).extract()[0] else: return '' article_list = response.xpath("//div[@class='post floated-thumb']") page_id_str = reFind("saveWebPage/(\d+).html", response.url) page_id = int(page_id_str) count = 0 for article in article_list: count += 1 item = BoleparseItem() item['id'] = (page_id - 1) * 20 + count item['title'] = find("div[@class='post-meta']/p[1]/a/@title", article) pTagStr = find("div[@class='post-meta']/p", article) item['publishTime'] = re.search("\d+/\d+/\d+", pTagStr).group(0) item['category'] = find("div[@class='post-meta']/p/a[2]/text()", article) item['digest'] = find("div[@class='post-meta']/span/p/text()", article) item['imgUrl'] = find("div[@class='post-thumb']/a/img/@src", article) item['detailUrl'] = find("div[@class='post-meta']/p/a[1]/@href", article) yield item
使用pymysql库将每一条文章信息item导入mysql数据库
下面一段代码需要修改2处:1.第4行的数据库名;2.第8行的数据库连接密码。 第24行default charset=utf8mb4创建表默认编码为utf8mb4,因为插入字符可能是4个字节编码。 第29、30行if len(item['imgUrl']) >= 200:item.pop('imgUrl')的作用: 防止图片是base64编码长度过大,遇到此类型的值则丢弃此字段。 通过这2个设置,增加了代码的健壮性,能够保证11172条数据都插入到数据库中。import pymysqlfrom time import timedef getConn(database ="bole"): args = dict( host = 'localhost', user = 'root', passwd = '... your password', charset = 'utf8mb4', db = database ) return pymysql.connect(**args)class BoleparsePipeline(object): startTime = time() conn = getConn() cursor = conn.cursor() drop_sql = "drop table if exists article" cursor.execute(drop_sql) conn.commit() create_sql = "create table article(id int primary key," \ "title varchar(200),publishtime varchar(30)," \ "category varchar(30),digest text," \ "detailUrl varchar(200),imgUrl varchar(200))default charset = utf8mb4;" cursor.execute(create_sql) conn.commit() def process_item(self, item, spider): if len(item['imgUrl']) >= 200: item.pop('imgUrl') fieldStr = ','.join(['`%s`' % k for k in item.keys()]) valuesStr = ','.join(['"%s"' % v for v in item.values()]) insert_sql = "insert into article(%s) values(%s)" % (fieldStr, valuesStr) self.cursor.execute(insert_sql) self.conn.commit() return item def close_spider(self, spider): print("程序总共运行%.2f秒" % (time() - self.startTime))
关键点是最后3行要开启管道,CONCURRENT_REQUESTS变量设置为96能够较好利用多线程性能
CONCURRENT_ITEMS设置为200能够加快并发管道处理item的速度。 ROBOTSTXT_OBEY设置为False,意思是不遵守爬虫协议,也称机器人协议。如果设置为True,即遵守爬虫协议,则可能访问受限。BOT_NAME = 'BoleParse'SPIDER_MODULES = ['BoleParse.spiders']NEWSPIDER_MODULE = 'BoleParse.spiders'ROBOTSTXT_OBEY = FalseCONCURRENT_REQUESTS = 96CONCURRENT_ITEMS = 200ITEM_PIPELINES = { 'BoleParse.pipelines.BoleparsePipeline': 300}
saveWebPage文件夹必须和启动cmd时处在相同的文件夹,只有这样才能运行成功。
如下图所示,powershell现在进入的目录是C:\Users\Administrator\Desktop\伯乐\BoleParse, 则saveWebPage文件夹也必须在C:\Users\Administrator\Desktop\伯乐\BoleParse中。 注意:读者的路径与本文不同;运行命令前建议先关闭Pycharm,否则可能卡顿程序运行结束后,查询插入数据的总条数,如下图所示:
数据库表中数据查看如下图所示:
进行此步骤时需要先把pipelines.py文件中的代码清空,然后把下面的代码插入其中。
数据库连接池方式进行数据库操作效率更高,因为是异步多线程运行,效率提高40%左右。 用twisted.enterprise.adbapi方法初始化一个数据库连接池对象。 该方法需要7个参数,其中dbapiName、cursorclass这2个和数据连接用的库有关, 其他5个参数是数据库连接设置,host、db、user、passwd、charset。 dbpool.runInteraction里面传入的第1个参数是函数对象,后面参数不定长。from twisted.enterprise import adbapiimport pymysqlimport timeclass BoleparsePipeline(object): def __init__(self): params = dict( dbapiName = 'pymysql', cursorclass=pymysql.cursors.DictCursor, host = 'localhost', db = 'bole', user = 'root', passwd = '...your password', charset = 'utf8', ) self.dbpool = adbapi.ConnectionPool(**params) self.startTime = time.time() self.dbpool.runInteraction(self.createTable) def createTable(self, cursor): drop_sql = "drop table if exists article" cursor.execute(drop_sql) create_sql = "create table article(id int primary key," \ "title varchar(200),publishtime varchar(30)," \ "category varchar(30),digest text," \ "detailUrl varchar(200),imgUrl varchar(200))" \ "default charset = utf8mb4;" cursor.execute(create_sql) def process_item(self, item, spider): self.dbpool.runInteraction(self.insert,item) return item def insert(self, cursor, item): try: if len(item['imgUrl']) >= 200: item.pop('imgUrl') fieldStr = ','.join(['`%s`' % k for k in item.keys()]) valuesStr = ','.join(['"%s"' % v for v in item.values()]) insert_sql = "insert into article(%s) values(%s)" % (fieldStr, valuesStr) cursor.execute(insert_sql) except Exception as e: with open("insert.log",'a+') as file: datetime = time.strftime('%Y-%m-%d %H:%M:%S') logStr = "%s log:插入第%d条数据发生异常\nreason:%s\n" file.write(logStr %(datetime,item['id'],str(e))) def close_spider(self, spider): print("程序总共运行%.2f秒" % (time.time() - self.startTime))
从下图中可以看出插入数据到mysql数据库中总共用时45.18秒
所以使用数据库连接池效率提高66.51/45.18-1=47%先从数据库中取出所有条目的id,赋值给id_list
result = set(id_list)^set(range(1,11173))第20行代码通过2个集合取差集找出缺少的条目。import pymysqldef getConn(database ="bole"): args = dict( host = 'localhost', user = 'root', passwd = '...your password', charset = 'utf8', db = database ) return pymysql.connect(**args)if __name__ == "__main__": conn = getConn() cursor = conn.cursor() sql = "select id from article" cursor.execute(sql) result = cursor.fetchall() id_list = [k[0] for k in result] result = set(id_list)^set(range(1,11173)) print(result)
转载地址:http://joxna.baihongyu.com/