课程名称:Scrapy打造搜索引擎(分布式爬虫)
课程章节:编写spider完成抓取过程
主讲老师:bobby
今天学习的内容包括:编写spider完成抓取过程
1.获取新闻列表页中的新闻url并交给scrapy进行下载后调用相应的解析方法
2.获取下一个的url并交给scrapy进行下载,下载完成后交给parse继续跟进
代码:
import re import json from urllib import parse import scrapy # scrapy是异步io框架,没有多线程,没有引入消息队列 from scrapy import Request from scrapy.loader import ItemLoader from ArticleSpider.items import JobBoleArticleItem from ArticleSpider.utils import common # 爬取http://news.cnblogs.com/博客园网站数据 class JobboleSpider(scrapy.Spider): name = 'jobbole' allowed_domains = ['news.cnblogs.com'] start_urls = ['https://news.cnblogs.com/'] custom_settings = { "COOKIES_ENABLED": True } """ 手动登录拿到cookie并放入到scrapy,降低难度 1.第一个案例是最简单的案例,需要登录才能访问 2.第二个案例 知乎:花费大量的时间讲解模拟登录 3.拉勾网 — crawlspider :拉勾反爬越来越严重,crawlspider用途不大 """ # spider运行起来,URL都会从此方法开始 def start_requests(self): # 入口可以模拟登录拿到cookie,selenium控制浏览器会被一些网站识别出来(例如:知乎、拉勾网) # 实例化一个浏览器 import undetected_chromedriver.v2 as uc chrome_driver = r"E:\Python\chromedriver.exe" # 后面讲解selenium的时候会下载chromedriver.exe browser = uc.Chrome(executable_path=chrome_driver) browser.get("https://account.cnblogs.com/signin") input("回车继续:") # 等待浏览器驱动加载完成 # 自动化输入、自动化识别滑动验证码并拖动整个自动化过程 # 手动登录后获取cookie值 cookies = browser.get_cookies() cookie_dict = {} # 将获取的cookie值转变为dict字典类型数据 for cookie in cookies: cookie_dict[cookie['name']] = cookie['value'] # 爬虫的时候不要过快,建议使用debug模式进行运行,否则网站会监测cookie值,然后禁止该cookie for url in self.start_urls: # 将cookie交给scrapy,那么后序的请求会沿用之前请求的cookie值吗? headers = { # headers将访问伪装成浏览器,防止被反爬 'user-agent': 'Mozilla/5.0 (Linux; Android 6.0; Nexus 5 Build/MRA58N) AppleWebKit/537.36 ' '(KHTML, like Gecko) Chrome/101.0.4951.64 Mobile Safari/537.36' } ''' dont_filter参数解析(url去重) 1.dont_filter是根据request来去重的,但是这里的request不是简单的url去重那么简单,而是scrapy有一套指纹的生成方法. 2.例如同一个url但是是post的请求,post参数不一样也不能认为是同一个请求,后面的scrapy-redis源码分析中会讲解到这个的, 如果你是get请求,那么你就简单的理解为通过url去重,这样的好处是这个数据爬取过你就不用再爬取了,不然重复爬取耗费性能啊, 3.但是说到这里我得提出另一个需求: 如果你的url相同的页面,可能数据每次访问都不一样,那么是不是该重复爬取呢? - 比如列表页 如果你的url相同的页面,虽然数据都一样,但是有可能用户会更新里面的内容呢? -比如文章详情页 4.所以我们不能说: 这个页面没有必要重复抓取, 但是另一方面大量的情况确实: 大部分的详情页个不会更新或者更新概率极低, 这个时候没有必要去抓取最新的数据,毕竟过滤抓取过的页面,这样效率高啊 所以:具体请求具体分析,scrapy提供了这个参数就是让你自己去决定这个数据是应该过滤掉还是可以重复抓取 ''' yield scrapy.Request(url, cookies=cookie_dict, headers=headers, dont_filter=True) # 能够进入parse()函数,则表示从http://news.cnblogs.com/下载已经完成 def parse(self, response, **kwargs): """ parse()方法作用: 1.获取新闻列表页中的新闻url并交给scrapy进行下载后调用相应的解析方法parse_detail()、parse_num() 2.获取下一页的url并交给scrapy进行下载,下载完成后交给parse()继续跟进 例如:第一页第一个新闻标题xpath路径(@取属性) 根据级一级一级往下查询,但是如果页面变化则会找不到 response.xpath("/html/body/div[2]/div[2]/div[4]/div[1]/div[2]/h2/a/@href") 根据id获取,id唯一 //*[@id="entry_721263"]/div[2]/h2/a/@href """ ''' url = response.xpath('//*[@id="entry_721263"]/div[2]/h2/a/@href').extract_first("") extract_first("")提取SelectList中的第一个值,没有则返回""空值 url = response.xpath('//div[@id="news_list"]/div[1]/div[2]/h2/a/@href').extract_first("") url = response.xpath('//div[@id="news_list"]//h2[@class="news_entry"]/a/@href').extract() ''' # 获取详情页的URL地址集 post_nodes = response.xpath("//div[@id='news_list']/div") # post_nodes = response.css('#news_list .news_block')[1:2] for post_node in post_nodes: ''' post_node.xpath开始以后就要是相对路径——.//h2[@class='news_entry']/a/@href 1.如果写成xpath('//*') //开头的就会从整个url开头找起 2.必须写成.// ''' post_url = post_node.xpath(".//h2[@class='news_entry']/a/@href").extract_first("") # 获取详情页URL地址(子路径"/n/721304/") # 返回至python则为/n/721304/,而浏览器中会自动将当前域名增加https://news.cnblogs.com/n/721304/ image_url = post_node.xpath(".//div[@class='entry_summary']/a/img/@src").extract_first("") # 图片地址 if image_url.startswith("//"): image_url = "https:" + image_url # image_url = post_node.css(".entry_summary a img::attr(src)").extract_first("") """ 1.urljoin()方法将两个链接参数拼接为完整URL,用于拼接url(无论post_url是完整还是非完整的url地址) 如果post_url是一个完整的url地址(即https://news.cnblogs.com/n/721304/),则不会将response.url拼接到前面 如果post_url不是一个完整的url地址(即/n/721304/),则会自动将https://news.cnblogs.com拼接到前面 2.callback表示Request()请求结束后,交给parse_detail()方法进行处理 注:一定不能写parse_detail(),写parse_detail()会运行该方法,但是此处需求是当Request下载完成后,调用该方法 parse_detail()则会有返回值,返回给callback=** 3.yield Request()后为什么没有跳转至parse_detail()方法而是继续for循环,为什么不是yield后立刻跳转至 parse_detail()方法? 原因:1.Scrapy是一个异步的框架,异步框架意味着当遇到一个url地址交出去之后,交出去之后会继续执行 2.服务器没有那么快返回,交出Request后服务器没有那么快返回数据,这时候就可以继续进行for循环,将 其中的数据逐步交出去 注:比较好的debug方式:不在下面的yield Request()打断点,而是在def parse_detail()方法中打断点 """ yield Request(url=parse.urljoin(response.url, post_url), meta={"front_image_url": image_url}, callback=self.parse_detail) # 提取下一页并交给scrapy进行下载 next_url = response.xpath("//a[contains(text(), 'Next >')]/@href").extract_first("") # next_url = response.css("div.pager a:last-child::text").extract_first("") # if next_url: # yield Request(url=parse.urljoin(response.url, next_url), callback=self.parse) # 新闻详情页面数据抓取 def parse_detail(self, response): pass