用scrapy框架做了一个简单的爬虫。算是练手。需求:爬取博客园新闻的标题、简要、发布日期。
打开cmd, 输入命令:
<code>scrapy shell https://news.cnblogs.com
view(response)</code>
查看元素所在位置。发现title位于h2 class="news_entry"下的a标签里,如:
可以用css很容易的提取。response.xpath('h2.news_entry a::text').extract_first()
发布日期位于<span class="gray">标签内,如:
用css也很容易提取。response.css('span.gray::text').extract_first()
但是,提取摘要的时候遇到了一个坑。估计是返爬的一个手法?位于<div class="entry_summary"标签里。如下图所示:
貌似很简单提取,实际上是一个坑。先说一下刚开始的思路。
response.xpath('.//div[@class="entry_summary"]//text()').extract_first()
得到结果如下:
'\n '
.extract_first()的作用是每次提取第一个,放在循环里。这样可以用yield函数遍历。我们先用.extract()提取所有的内容,得到了一个列表。也可以不加.extract(),得到一个原始的xpath元素。如下:
发现了问题所在://text()方法是可以提取所有的文本,但是结果是一个列表,不适合做去除空格的进一步动作。而且,空格回车和文本本来应该是一个整体,但是结果却分开了,以至于用.extract_first()提取到的是空格回车。
网上搜索答案,有人用string()方法可以跨标签得到文本。用了一下,并没有什么作用。而且,不能遍历,只能得到第一个元素。也就是<code>'\n '</code>
貌似无解了?我又继续网上搜索答案,路人甲的一篇文章给我了思路。他在文章里说,尽量用contains(.),而不是//text()。因为前者会把空格回车和文字作为一个整体提取,属于模糊查询,从而可以结合string()方法来获得文本,也可以结合.strip()去除多余的空格回车。而//text()得到的是一个列表。里面的元素分开了。
如下:
response.xpath('string(.//div[@class="entry_summary"][contains(.,text())])').extract_first().strip()
因为contains()属于模糊查找,连HTML标签内容也匹配在内了。所以用string方法去除标签。然后用.strip()去除空格。
结果相当完美:
附上完整代码:
import scrapy class NewsSpider(scrapy.Spider): name = "newss" start_urls = ['https://news.cnblogs.com/'] # MAX_DOWNLOAD_NUMB = 100 def parse(self, response): for news in response.css('div.news_block'): title = news.css('h2.news_entry a::text').extract_first() summary = news.xpath('string(.//div[@class="entry_summary"][contains(.,text())])').extract_first().strip() #//*[@id="entry_665993"]/div[2]/div[1]/text() time = news.css('span.gray::text').extract_first() yield { 'title':title, 'summary':summary, 'time':time, } next_url = response.css('div.pager a:last-of-type::attr(href)').extract_first() if next_url: next_url = response.urljoin(next_url) yield scrapy.Request(next_url,callback=self.parse)
执行scrapy crawl newss -o news.csv
得到所有的文章标题、摘要、发布时间列表。