一、环境
- windows
- python3.6.4
scrapy1.5.1
明白Python的基本语法系列
二、知识点
- xpath
文字内容爬取并存本地文件
翻页爬取
图片爬取并存本地
简单的反爬虫
数据存数据库(mysql)
日志
三、项目构建及文件说明
1、项目创建
scrapy startproject Douban
2、项目初始化
cd Douban scrapy genspider douban "douban.com"
3、各文件说明
四、xpath解析说明
以Chrome插件(XPath Helper)为例。//ol[@class='grid_view']/li/div[@class='item']即可解析出所有的电影信息模块,然后循环遍历进行处理即可。
PS:“//”即代表从任意路径下开始寻找
五、字段设置
即 item.py 文件。# 电影名字 film_name = scrapy.Field() # 导演和主演名字 director_performer_name = scrapy.Field() # 主演名字 # performer_name = scrapy.Field() # 电影上映年份 film_year = scrapy.Field() # 电影国家 film_country = scrapy.Field() # 电影类型 film_type = scrapy.Field() # 电影评分 film_rating = scrapy.Field() # 电影评论人数 film_reviews_num = scrapy.Field() # 电影经典语句 film_quato = scrapy.Field() # 电影图片 film_img_url = scrapy.Field()
六、爬虫编写
即 douban.py 文件。以下代码,注释很详细,细节暂不赘述,简单提一下 yield 的用法:
yield 是个很重要的语法,有着 return 的部分功能,但完全不同于 return。
return 会返回信息并且终止当前的方法,而 yield 虽然也会返回一个信息给调用者,但是调用者使用完了之后程序还会回到此处继续执行。
比如用在此爬虫的 for 循环中的妙处是:此处生成 item 之后返回给调度器进行相关的处理,然后程序再回到这里继续运行,即继续下一个循环,然后再生成一个新的 item 提供给调度器,如此往复,直到循环结束。
# 爬虫名称(必须唯一) name = 'douban' # 非此域名下的链接均不进行爬取 allowed_domains = ['douban.com'] base_url = 'https://movie.douban.com/top250' off_set = '?start=0&filter=' # 起始的爬取地址 start_urls = [base_url + off_set] # 每次的爬取都会默认走这个 parse 方法 def parse(self, response): # xpath 解析出每个电影的信息模块 films = response.xpath("//ol[@class='grid_view']/li/div[@class='item']") # 遍历每个电影模块 for film in films: # 创建电影信息存储的item对象 item = DoubanItem() # 标题 titles = film.xpath("./div[@class='info']/div[@class='hd']/a/span/text()").extract() film_name = '' # 拼接电影名 for title in titles: film_name += title.strip() # 电影信息 infos = film.xpath("./div[@class='info']/div[@class='bd']/p/text()").extract() director_performer_name = "" for temp in infos[0]: director_performer_name += temp.strip() year_country = infos[1] film_year = year_country.split("/")[0].strip() film_country = year_country.split("/")[1].strip() film_type = year_country.split("/")[2].strip() # 电影评分 film_rating = film.xpath("./div[@class='info']/div[@class='bd']/div[@class='star']/span[@class='rating_num']/text()").extract()[0].strip() # 电影参与评论人数 film_reviews_num = film.xpath("./div[@class='info']/div[@class='bd']/div[@class='star']/span[last()]/text()").extract()[0].strip()[:-3] # 电影经典语句 film_quato = "" film_quato_temp = film.xpath("./div[@class='info']/div[@class='bd']/p[@class='quote']/span/text()").extract() if film_quato_temp: film_quato = film_quato_temp[0].strip() # 电影图片链接 film_img_url = film.xpath("./div[@class='pic']/a/img/@src").extract()[0].strip() # item 字段赋值 item['film_name'] = film_name item['director_performer_name'] = director_performer_name # item['director_name'] = director_name # item['performer_name'] = performer_name item['film_year'] = film_year item['film_country'] = film_country item['film_type'] = film_type item['film_rating'] = film_rating item['film_reviews_num'] = film_reviews_num item['film_quato'] = film_quato item['film_img_url'] = film_img_url # 返回 item 进行解析,解析完了之后再回到这里继续运行 yield item # 翻页 # 解析出下一页 next_url = response.xpath("//div[@class='paginator']/span[@class='next']/a/@href").extract() if next_url: # 如果下一页存在的话再进行请求,并传递回调函数 parse() yield scrapy.Request(self.base_url + next_url[0], self.parse)
七、“管道”说明
即 pipelines.py 文件。说明:正如其名“管道”,它是用来处理 item 的,所以,我们可以写多个“管道”文件来处理 item,但是要注意: ① 每个“管道”处理完之后记得 return item,否则后续管道无法再进行处理,毕竟拿不到了嘛; ② 管道是有执行顺序的,所以需要我们进行定义其顺序(settings.py 文件),数字小,先执行:
# Configure item pipelines # See https://doc.scrapy.org/en/latest/topics/item-pipeline.html ITEM_PIPELINES = { 'Douban.pipelines.DoubanMoviePipeline': 300, 'Douban.pipelines.DoubanImgPipeline': 301, 'Douban.pipelines.DoubanDBPipeline': 400 }
1、文本内容存本地文件
即配置里面的:'Douban.pipelines.DoubanMoviePipeline': 300, 也是比较简单,看下文代码的注释即可,但是要注意编码。class DoubanMoviePipeline(object): """ 处理电影信息 """ def __init__(self): # 初始化:文件打开 self.f = codecs.open("doubanData.json", mode="w", encoding="utf-8") def process_item(self, item, spider): # 内容,结尾增加了换行 content = json.dumps(dict(item), ensure_ascii=False) + ",\n" # 内容写入文件 self.f.write(content) # 一定要记得 return,否则之后的 pipeline 拿不到 item,也就没法继续处理了 return item def close_spider(self, spider): # 爬虫关闭时进行:文件关闭 self.f.close()
2、图片内容保存本地
我们写在同一个“管道”文件里面。注意继承类:ImagesPipeline,源码见:D:\IT\Python\Python36\Lib\site-packages\scrapy\pipelines\images.py 注意在 settings.py 中设置图片的下载路径:IMAGES_STORE = "D:\IT\Python\workspace\SpiderDemo\Douban\images\\" 代码同样比较简单,见下面的注释即可,注意,此处进行了文件重命名操作,并有打异常日志,日志后面会讲到。
class DoubanImgPipeline(ImagesPipeline): """ 处理图片信息 """ def get_media_requests(self, item, info): """ 图片下载 """ film_img_url = item['film_img_url'] yield scrapy.Request(film_img_url) def item_completed(self, results, item, info): """ 图片重命名 """ # 获取文件下载的路径 path = [x['path'] for ok, x in results if ok] # 原始的完整路径 film_img_disk_url1 = settings.IMAGES_STORE + path[0] # 准备存放的新的完整路径 film_img_disk_url = settings.IMAGES_STORE + 'full\\' + item['film_name'].split("/")[0].strip() + ".jpg" try: # 重命名 os.rename(film_img_disk_url1, film_img_disk_url) except Exception as error: Logger(logLevel='error').getLogger().error("图片重命名失败,异常信息:%s" % error) pass return item
3、数据存数据库
还是写在同一个“管道”文件里面。
“管道”配置为:'Douban.pipelines.DoubanDBPipeline': 400 settings.py 中配置数据库信息:# mysql 设置 MYSQL_HOST = 'localhost' MYSQL_PORT = 3380 MYSQL_DBNAME = 'scrapy' MYSQL_USER = 'root' MYSQL_PASSWD = 'root'代码理解也不困难,见注释即可,此处进行了简单的查重处理。
class DoubanDBPipeline(object): """ 数据存入mysql """ def __init__(self): # 连接数据库 self.connect = pymysql.connect( host=settings.MYSQL_HOST, port=settings.MYSQL_PORT, db=settings.MYSQL_DBNAME, user=settings.MYSQL_USER, passwd=settings.MYSQL_PASSWD, charset='utf8mb4', use_unicode=True ) self.cursor = self.connect.cursor() def process_item(self, item, spider): try: # 数据库查重 self.cursor.execute( """select film_name from douban_movie_top_250 where film_name = %s and film_img_url = %s""", (item['film_name'], item['film_img_url']) ) # 查重 repetition = self.cursor.fetchone() if repetition: # 数据重复 Logger().getLogger().info("数据重复,film_name: %s,film_img_url:%s" % (item['film_name'], item['film_img_url'])) pass else: # 插数据 self.cursor.execute( """insert into douban_movie_top_250(film_name, director_performer_name, film_year, film_country, film_type, film_rating, film_reviews_num, film_quato, film_img_url) VALUE (%s, %s, %s, %s, %s, %s, %s, %s, %s)""", ( item['film_name'], item['director_performer_name'], item['film_year'], item['film_country'], item['film_type'], item['film_rating'], item['film_reviews_num'], item['film_quato'], item['film_img_url'] ) ) # sql提交 self.connect.commit() except Exception as error: Logger(logLevel='error').getLogger().error("数据插入数据库失败", error) return item def close_spider(self, spider): # 关闭数据库连接 self.connect.close()
八、简单的反爬虫
1、添加用户代理
即添加 USER_AGENT,用于伪装浏览器 在 settings.py 中进行配置:# Crawl responsibly by identifying yourself (and your website) on the user-agent USER_AGENT = 'Mozilla/5.0 (Windows NT 6.3; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3538.110 Safari/537.36'
2、不遵守 robots 协议
在 settings.py 中进行配置:# Obey robots.txt rules ROBOTSTXT_OBEY = False
3、请求间隙
即防止请求过于频繁。在 settings.py 中进行配置,单位是秒:
DOWNLOAD_DELAY = 0.25
4、设置 cookies
此处 Douban 的爬虫我们没有进行设置,github 中拉勾网的爬虫中有最简单设置。即在爬虫代码中设置 cookie,然后在每个 Request 请求中直接添加。
yield scrapy.Request(job_url, cookies=self.cookie, meta={'item': item}, callback=self.parse_url)或者完美一点的做法,应该是在中间件中设置,即在 middlewares.py 文件中进行配置。
九、日志
日后写一篇详细的介绍,此处暂不进行细说。自定义的简单的日志模块为项目中的 logger.py 文件,使用方法见注释。
十、运行
1、查找可运行的 scrapy 项目
scrapy list 2、运行爬虫
scrapy crawl douban 3、运行爬虫并将 item 信息输出至文件
scrapy crawl douban -o doubanData.json 4、新建执行文件
新建 run.py 执行文件文件内容为:
from scrapy.cmdline import execute execute(['scrapy', 'crawl', 'douban'])以后直接执行这个 python 文件即可。
文章评论