目录
scrapy爬虫模板
scrapy为我们提供了多种爬虫模板,默认使用basic模板。查看scrapy为我们有多少爬虫模板,可以通过命令进行查看:scrapy genspider --list
使用crawl模板创建CrawlSpider爬虫
使用命令 :scrapy genspider -t crawl lagou www.lagou.com
,其中,-t
为指定创建的模板,-t crawl
即为使用crawl模板创建CrawlSpider爬虫。
根目录变化导致创建CrawlSpider报错
在创建CrawlSpider爬虫模板的时候会报ModuleNotFoundError: No module named ‘utils’的错误。这是因为系统认为最顶层的、第一个的文件夹是根目录,但是我们选择的根目录却不一定是系统认为的那一个才是根目录。
如何告诉系统,以我指定的文件夹作为根目录?那我们可以到settings.py中去设定一下,告诉系统我们的真实想法。
1 2 3 4 5 6 7 8 9 10 11 |
import sys import os # 项目的根目录 BASE_DIR = os.path.dirname(os.path.abspath(os.path.dirname(__file__))) # print(BASE_DIR) #D:\PycharmProjects\scrapytest\ArticleSpider # 使用os.path.join(BASE_DIR,'ArticleSpider')合并成指定的根目录 # sys.path是python的库类地址寻找列表, # 把指定的根目录,插入到第一位,告诉系统我指定目录作为根目录 sys.path.insert(0,os.path.join(BASE_DIR,'ArticleSpider')) |
然后,重新执行scrapy genspider -t crawl lagou www.lagou.com
就可以正常创建文件了。
还有一个问题,就是,既然在settings.py都设置好了,为什么编辑器引入文件代码还是有红波浪线。这个是pycharm编辑器的问题,与settings.py里的代码设置是不相干的,没有任何联系。settings.py的代码是整个项目的配置,执行命令行,首先会读取这个配置文件。而红波浪线是由于pycharm在整个项目文件的索引上,以最顶层的目录作为根目录,pycharm不可能会自动读取settings.py里的代码,所以,红波浪线是pycharm自身的问题。如何解决呢?只要通过pycharm的设置,告诉pycharm,你指定的根目录在哪里就可以了。
CrawlSpider、LinkExtractors、Rule及爬虫示例
CrawlSpider
、LinkExtractors
、Rule
是scrapy
框架中的类,其中CrawlSpider
是Spider
的派生类,具有更多的方法和功能,LinkExtractor
类是用作提取链接的,Rule
表示的是爬取的规则。
CrawlSpider
CrawlSpider
是Spider
的派生类,Spider
类的设计原则是只爬取start_urls
中的url
,而CrawlSpider
类定义了一些规则(rules
)来提供跟进链接(link
)的方便机制,从爬取的网页中获取link
并继续爬取的工作更适合。
CrawlSpider除了Spider继承过来的属性外,还提供了一个新的属性:
rules
包含一个或多个Rule
对象的集合。每个Rule
对爬取网站的动作定义了特定规则。Rule
对象会在下面介绍。
如果多个Rule
匹配了相同的链接,则根据他们在本属性中被定义的顺序,第一个会被使用。
CrawlSpider也提供了一个可复写的方法:
parse_start_url(response)
当start_url的请求返回时,该方法被调用。该方法分析最初的返回值并必须返回一个Item
对象或一个Request
对象或者一个可迭代的包含二者的对象
当编写爬虫规则时,请避免使用parse
作为回调函数。 由于CrawlSpider
使用parse
方法来实现其逻辑,如果 您覆盖了parse
方法,CrawlSpider
将会运行失败。
LinkExtractor
class scrapy.linkextractors.LinkExtractor
LinkExtractor
是从网页(scrapy.http.Response
)中抽取会被follow
的链接的对象。
LinkExtractor
在CrawlSpider
类(在Scrapy
可用)中使用, 通过一套规则,但你也可以用它在你的Spider
中,即使你不是从CrawlSpider
继承的子类, 因为它的目的很简单: 提取链接。
每个LinkExtractor有唯一的公共方法是 extract_links(),它接收一个 Response 对象,并返回一个 scrapy.link.Link 对象。
Link Extractors要实例化一次,并且 extract_links 方法会根据不同的 response 调用多次提取链接。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class scrapy.linkextractors.LinkExtractor( allow = (), deny = (), allow_domains = (), deny_domains = (), deny_extensions = None, restrict_xpaths = (), tags = ('a','area'), attrs = ('href'), canonicalize = True, unique = True, process_value = None ) |
主要参数:
allow
:满足括号中”正则表达式”的值会被提取,如果为空,则全部匹配。
deny
:与这个正则表达式(或正则表达式列表)不匹配的url一定不提取
allow_domains
:会被提取的连接的domains
deny_domains
:一定不会被提取链接的domains。
restrict_xpaths
:使用xpath表达式,和allow共同作用过滤链接。
Rule
Rule
对象是一个爬取规则的类。
其类的定义如下:
class scrapy.contrib.spiders.Rule(link_extractor,callback=None,cb_kwargs=None,follow=None,process_links=None,process_request=None)
link_extractor
:是一个Link Extractor对象。其定义了如何从爬取到的页面提取链接。
callback
:是一个callable
或string
(该Spider
中同名的函数将会被调用)。从link_extractor
中每获取到链接时将会调用该函数。该回调函数接收一个response
作为其第一个参数,并返回一个包含Item
以及Request
对象(或者这两者的子类)的列表。
cb_kwargs
:包含传递给回调函数的参数(keyword argument)的字典。
follow
:是一个boolean
值,指定了根据该规则从response
提取的链接是否需要跟进。如果callback
为None
,follow
默认设置True
,否则默认False
。
process_links
:是一个callable
或string
(该Spider
中同名的函数将会被调用)。从link_extrator
中获取到链接列表时将会调用该函数。该方法主要是用来过滤。
process_request
:是一个callable
或string
(该spider
中同名的函数都将会被调用)。该规则提取到的每个request
时都会调用该函数。该函数必须返回一个request
或者None
。用来过滤request
。
CrawlSpider 模板
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 |
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule class LagouSpider(CrawlSpider): # spider的唯一名称 name = 'lagou' allowed_domains = ['www.lagou.com'] # 开始爬取的url start_urls = ['https://www.lagou.com/'] # 设置解析link的规则,callback是指解析link返回的响应数据的的方法 # allow里是允许爬取的url地址正则匹配 rules = ( Rule(LinkExtractor(allow=r'Items/'), callback='parse_item', follow=True), ) # callback的具体的处理方法 def parse_item(self, response): i = {} #i['domain_id'] = response.xpath('//input[@id="sid"]/@value').extract() #i['name'] = response.xpath('//div[@id="name"]').extract() #i['description'] = response.xpath('//div[@id="description"]').extract() return i |
局部配置setting,携带header头信息
custom_settings
是一个用于配置局部setting的,scrapy提供的配置字典。其中,DEFAULT_REQUEST_HEADERS
可以编写头信息。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
class LagouSpider(CrawlSpider): name = 'lagou' allowed_domains = ['www.lagou.com'] start_urls = ['https://www.lagou.com/'] #是对setting中的文件内容进行覆盖。 custom_settings = { "COOKIES_ENABLED": False, # 默认enable,爬取登录后数据时需要启用 "DOWNLOAD_DELAY": 1, # 下载延时,默认是0 'DEFAULT_REQUEST_HEADERS': { # header头信息 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Connection': 'keep-alive', 'Cookie': 'user_trace_token=20171015132411-12af3b52-3a51-466f-bfae-a98fc96b4f90; LGUID=20171015132412-13eaf40f-b169-11e7-960b-525400f775ce; SEARCH_ID=070e82cdbbc04cc8b97710c2c0159ce1; ab_test_random_num=0; X_HTTP_TOKEN=d1cf855aacf760c3965ee017e0d3eb96; showExpriedIndex=1; showExpriedCompanyHome=1; showExpriedMyPublish=1; hasDeliver=0; PRE_UTM=; PRE_HOST=www.baidu.com; PRE_SITE=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DsXIrWUxpNGLE2g_bKzlUCXPTRJMHxfCs6L20RqgCpUq%26wd%3D%26eqid%3Dee53adaf00026e940000000559e354cc; PRE_LAND=https%3A%2F%2Fwww.lagou.com%2F; index_location_city=%E5%85%A8%E5%9B%BD; TG-TRACK-CODE=index_hotjob; login=false; unick=""; _putrc=""; JSESSIONID=ABAAABAAAFCAAEG50060B788C4EED616EB9D1BF30380575; _gat=1; _ga=GA1.2.471681568.1508045060; LGSID=20171015203008-94e1afa5-b1a4-11e7-9788-525400f775ce; LGRID=20171015204552-c792b887-b1a6-11e7-9788-525400f775ce', 'Host': 'www.lagou.com', 'Origin': 'https://www.lagou.com', 'Referer': 'https://www.lagou.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', } } |
CrawlSpider实例
lagou.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 |
# -*- coding: utf-8 -*- import scrapy from scrapy.linkextractors import LinkExtractor from scrapy.spiders import CrawlSpider, Rule from items import LagouItemLoader,LagouJobItem from utils.common import get_md5 import datetime class LagouSpider(CrawlSpider): name = 'lagou' allowed_domains = ['www.lagou.com'] start_urls = ['https://www.lagou.com/'] #是对setting中的文件内容进行覆盖。 custom_settings = { "COOKIES_ENABLED": False, # 默认enable,爬取登录后数据时需要启用 "DOWNLOAD_DELAY": 1, # 下载延时,默认是0 'DEFAULT_REQUEST_HEADERS': { # header头信息 'Accept': 'application/json, text/javascript, */*; q=0.01', 'Accept-Encoding': 'gzip, deflate, br', 'Accept-Language': 'zh-CN,zh;q=0.8', 'Connection': 'keep-alive', 'Cookie': 'user_trace_token=20171015132411-12af3b52-3a51-466f-bfae-a98fc96b4f90; LGUID=20171015132412-13eaf40f-b169-11e7-960b-525400f775ce; SEARCH_ID=070e82cdbbc04cc8b97710c2c0159ce1; ab_test_random_num=0; X_HTTP_TOKEN=d1cf855aacf760c3965ee017e0d3eb96; showExpriedIndex=1; showExpriedCompanyHome=1; showExpriedMyPublish=1; hasDeliver=0; PRE_UTM=; PRE_HOST=www.baidu.com; PRE_SITE=https%3A%2F%2Fwww.baidu.com%2Flink%3Furl%3DsXIrWUxpNGLE2g_bKzlUCXPTRJMHxfCs6L20RqgCpUq%26wd%3D%26eqid%3Dee53adaf00026e940000000559e354cc; PRE_LAND=https%3A%2F%2Fwww.lagou.com%2F; index_location_city=%E5%85%A8%E5%9B%BD; TG-TRACK-CODE=index_hotjob; login=false; unick=""; _putrc=""; JSESSIONID=ABAAABAAAFCAAEG50060B788C4EED616EB9D1BF30380575; _gat=1; _ga=GA1.2.471681568.1508045060; LGSID=20171015203008-94e1afa5-b1a4-11e7-9788-525400f775ce; LGRID=20171015204552-c792b887-b1a6-11e7-9788-525400f775ce', 'Host': 'www.lagou.com', 'Origin': 'https://www.lagou.com', 'Referer': 'https://www.lagou.com/', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36', } } # url的爬取规则 rules = ( Rule(LinkExtractor(allow=("zhaopin/.*",)), follow=True), Rule(LinkExtractor(allow=("gongsi/j\d+.html",)), follow=True), Rule(LinkExtractor(allow=r'jobs/\d+.html'), callback='parse_job', follow=True), ) def parse_job(self, response): item_loader = LagouItemLoader(item=LagouJobItem(), response=response) item_loader.add_css("title", ".job-name::attr(title)") item_loader.add_value("url", response.url) item_loader.add_value("url_object_id", get_md5(response.url)) item_loader.add_css("salary", ".job_request .salary::text") item_loader.add_xpath("job_city", "//*[@class='job_request']/p/span[2]/text()") item_loader.add_xpath("work_years", "//*[@class='job_request']/p/span[3]/text()") item_loader.add_xpath("degree_need", "//*[@class='job_request']/p/span[4]/text()") item_loader.add_xpath("job_type", "//*[@class='job_request']/p/span[5]/text()") item_loader.add_css("tags", ".position-label li::text") item_loader.add_css("publish_time", ".publish_time::text") item_loader.add_css("job_advantage", ".job-advantage p::text") item_loader.add_css("job_desc", ".job_bt div") item_loader.add_css("job_addr", ".work_addr") item_loader.add_css("company_url", "#job_company dt a::attr(href)") item_loader.add_css("company_name", "#job_company dt a img::attr(alt)") item_loader.add_value("crawl_time", datetime.datetime.now()) job_item = item_loader.load_item() return job_item |
items.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 |
import scrapy import datetime from scrapy.loader.processors import MapCompose, TakeFirst, Join from scrapy.loader import ItemLoader from utils.common import extract_num,remove_splash,handdle_jobaddr from settings import SQL_DATETIME_FORMAT,SQL_DATE_FORMAT from w3lib.html import remove_tags #去掉html的tag # 自定义拉钩ItemLoader class LagouItemLoader(ItemLoader): default_output_processor = TakeFirst() # 拉钩网职位信息item class LagouJobItem(scrapy.Item): title = scrapy.Field() url = scrapy.Field() url_object_id = scrapy.Field() salary = scrapy.Field() job_city = scrapy.Field( input_processor = MapCompose(remove_splash), ) work_years = scrapy.Field( input_processor=MapCompose(remove_splash), ) degree_need = scrapy.Field( input_processor=MapCompose(remove_splash), ) job_type = scrapy.Field() publish_time = scrapy.Field() job_advantage = scrapy.Field() job_desc = scrapy.Field() job_addr = scrapy.Field( input_processor=MapCompose(remove_tags,handdle_jobaddr), ) company_name = scrapy.Field() company_url = scrapy.Field() tags = scrapy.Field( input_processer=Join(','), ) crawl_time = scrapy.Field() # 重写get_insert_sql,pipelines统一调用这个方法, # pipelines不需要做任何修改 def get_insert_sql(self): # 定义SQL语句 # on DUPLICATE key update content=values (content) # 只需要更新的字段 insert_sql = """ insert into lagou_job (title,url,url_object_id,salary,job_city,work_years,degree_need,job_type,publish_time,job_advantage,job_desc,job_addr,company_name,company_url,tags,crawl_time) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s) on DUPLICATE key update salary=values (salary),job_desc=values (job_desc) """ # 组装参数 params = (self['title'],self['url'],self['url_object_id'],self['salary'],self['job_city'],self['work_years'],self['degree_need'],self['job_type'],self['publish_time'],self['job_advantage'],self['job_desc'],self['job_addr'],self['company_name'],self['company_url'],self['tags'],self['crawl_time'].strftime(SQL_DATETIME_FORMAT)) # 把SQL语句与params参数返回 return insert_sql,params |
pipelines.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 |
import MySQLdb # 导入MySQL类库 # 导入cursor,cursorclass = MySQLdb.cursors.DictCursor才会生效 import MySQLdb.cursors # adbapi可以使MySQL的操作变成异步的操作 from twisted.enterprise import adbapi class MysqlTwistedPipeline(object): # cls 代表 MysqlTwistedPipeline() # from_settings 方法名固定,scrapy通过这个方法读取settings配置 @classmethod def from_settings(cls, settings): dbparms = dict( host = settings['MYSQL_HOST'], db = settings['MYSQL_DBNAME'], user = settings['MYSQL_USER'], password = settings['MYSQL_PASSWORD'], charset = 'utf8', use_unicode = True, cursorclass = MySQLdb.cursors.DictCursor,# 指定cursor ) # adbapi提供的连接池,能让MySQL异步操作 # MySQLdb 为数据库模块 # **dbparms 为链接数据库的参数 dbpool = adbapi.ConnectionPool('MySQLdb',**dbparms) # 实例化自身类MysqlTwistedPipeline(), # 并把创建好的连接池dbpool作为参数传递过去 return cls(dbpool) # 接收连接池dbpool def __init__(self, dbpool): self.dbpool = dbpool def process_item(self, item, spider): # 使用Twisted 将MySQL插入变成异步执行 query = self.dbpool.runInteraction(self.do_insert, item) query.addErrback(self.handle_MysqlErr, item, spider) # 异常处理 # 具体的异常处理 def handle_MysqlErr(self, failure, item, spider): print(failure) pass # 具体的插入操作 def do_insert(self, cursor, item): # 插入操作 insert_sql,params = item.get_insert_sql() # 执行sql语句 cursor.execute(insert_sql, params) # 不需要commit,会自动commit |