经过前面两章简书做基础,今天我们爬取相对难一点的项目-爬取用户的博文相关信息-博文标题、标题链接、点击数、评论数等;说到这里,有很多同学都做不住了:前面不是也是获取过网页的信息么,这些信息不是和前面一个样;其实,有这个想法是不错的,刚开始我也有这个想法,但是当我真正去爬取的时候才发现,“点击数“ 和 “评论数” 是通过 js 动态加载的,而博文标题和博文链接是静态的,因此这个实战同时涉及到 静态 和 动态 网页数据的获取。
废话不多说,接下来我们开搞,首先看下效果图:
6.png这里坑就坑在效果图和显示中是不一样的,不过你怎么使用 xpath 表达式获取“点击数”和“评论数”就是只能得到默认值,这让人很是抓狂,大家可能有点不相信,那么我们就先用静态的方式去获取并输出看看效果先
好,我们开始建立爬取博客项目:
scrapy startproject myblogproject(名字可自定义)
然后根据我们需要爬取的需求信息在 items.py 文件中定义爬取字段:
import scrapy
class MyblogprojectItem(scrapy.Item):
# define the fields for your item here like:
# 文章名
name = scrapy.Field()
# 文章地址
url = scrapy.Field()
# 文章阅读数
hits = scrapy.Field()
# 文章评论数
comment = scrapy.Field()
实体字段定义好之后我们创建一个基于 basic 版本的爬虫文件:
scrapy genspider -t basic blogspider(爬虫文件名可自定义) hexun.com
聪明的同学在编辑爬虫文件之前会想到要去观察下换页 url 的变化:
7.png从图中红色方括号可以清晰的看到,第一个是用户ID,第二个是页码
接下来开始编辑爬虫文件:
# -*- coding: utf-8 -*-
import scrapy
from myblogproject.items import MyblogprojectItem
import re
import requests
class BlogspiderSpider(scrapy.Spider):
name = 'blogspider'
allowed_domains = ['hexun.com']
offset = 1 #偏移量
user_id = "toureiffel" #用户id,要爬取不同用户的中的文章数据换一下用户ID即可
# user_id = "xqw55555"
# user_id = "tianyahaijiaoke"
# user_id = "tjhyb2011"
my_url = 'http://'+ user_id +'.blog.hexun.com/p1/default.html'
start_urls = [my_url]
def parse(self, response):
item = MyblogprojectItem()
node_list = response.xpath('//div[@class="Article"]')
for node in node_list:
item['name'] = node.xpath('./div/span/a/text()').extract_first()
item['url'] = node.xpath('./div/span/a/@href').extract_first()
#由于点击数和评论数是动态获取的,因此不能直接在源码中直接抓取
item["hits"] = node.xpath('./div[2]/div[2]/span/text()').extract_first() # 0
item["comment"] = node.xpath('./div[2]/div[2]/a[2]/span/text()').extract_first() # 0
print(item['name'], item['url'], item["hits"], item["comment"])#输出测试
yield item
接下来一步就是在设置文件中做相应的反屏蔽设置:
#以下几个设置可以基本达到被反屏蔽的效果(当然这只是简单的几种方式而已)
DEFAULT_REQUEST_HEADERS = {
'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.139 Safari/537.36',
'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
'Accept-Language': 'en',
}
ROBOTSTXT_OBEY = False
COOKIES_ENABLED = False
我们接下来先测试一下运行效果:
8.png为何会出现如此奇葩的事情?观察了好久,终究还是发现了那两个玩意是动态加载的,乖乖了。。。接下来我们一起去看下它到底是张啥样的:
9.png知道了这些数据的格式并且知道了这个链接,我们把链接复制一份在浏览器中打开发现其数据和刚才我们在开发者模式看到的一模一样,那么我们如何使用代码获取呢???好,我们打开网页的源代码,然后将动态链接复制过来查找一下,发现这个链接其实就在我们的源代码中有:
10.png好了,完事具备,就差我们如何去修改爬虫文件了:
# -*- coding: utf-8 -*-
import scrapy
from myblogproject.items import MyblogprojectItem
import re
import requests
class BlogspiderSpider(scrapy.Spider):
name = 'blogspider'
allowed_domains = ['hexun.com']
offset = 1 #偏移量
user_id = "toureiffel" #用户id,要爬取不同用户的中的文章数据换一下用户ID即可
# user_id = "xqw55555"
# user_id = "tianyahaijiaoke"
# user_id = "tjhyb2011"
my_url = 'http://'+ user_id +'.blog.hexun.com/p1/default.html'
start_urls = [my_url]
def parse(self, response):
item = MyblogprojectItem()
#编写获取点击数和评论数 链接 的正则表达式
reg = r'<script type="text/javascript" src="(http://click.tool.hexun.com/.*?)">'
myurl = re.compile(reg, re.S).findall(str(response.body)) #注:这里必先把字节转换成字符串才能使用正则
#print("提取的点赞和评论连接为-> "+ myurl[0]) #输出测试
headers = { "User-Agent":"Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/64.0.3282.186 Safari/537.36" }
html = requests.get(myurl[0], headers=headers)#请求
html.encoding = "utf-8" #编码
data = html.text #获取源码文本
#提取点击数的正则表达式
hits_reg = r"click\d*','(\d*)'"
hits_list = re.compile(hits_reg, re.S).findall(data)#获取点击数的列表
#提取评论数的正则表达式
common_reg = r"comment\d*','(\d*)'"
common_list = re.compile(common_reg, re.S).findall(data)#获取评论数的列表
#赋值给变量
item['hits'] = hits_list
item['comment'] = common_list
#使用 xpath 表达式获取
item['name'] = response.xpath('//div[@class="Article"]/div/span/a/text()').extract()
item['url'] = response.xpath('//div[@class="Article"]/div/span/a/@href').extract()
for i in range(0, len(item["name"])):
#保存为csv文件
blog_list = [item["name"][i], item["url"][i], item["hits"][i], item["comment"][i]]
print("测试数据-> "+ str(blog_list))#输出测试
yield item
费了那么大的力气,接下来运行测试一下:
11.png这下终于出来了,哈哈哈,咱们一鼓做气,把分页页搞定了,分页爬取有两种方式:一种是判断“下一页”按钮,另一种就是拼接 url 地址(这两种我们前面一个文章都详细的分析过了,在这里我们使用拼接url方式,当然如果使用拼接并且不用我们每次都去数论文的总数那才叫绝),修改代码之前我们先看下源码的截图:
12.png看到了吧,哈哈哈,总页数是在源码中可以获取的,这样我们就可以开始分页的去爬取了,哈哈哈,在原来的爬虫文件爬取一页代码的下面添加如下代码即可:
#获取总页数
page_reg = r'blog.hexun.com/p(.*?)/'
sum_page_list = re.compile(page_reg, re.S).findall(str(response.body))
#如果用户的文章只有一页的话就无法得到总页数,因此我们必须写个兼容这种情况的方法
sum_page = 0 #存储总页数的变量
if(len(sum_page_list) >= 2):
sum_page = sum_page_list[-2]
else:
sum_page = 1 #如果只有一页则赋值给1即可
if self.offset < int(sum_page): #偏移量必须要小于总页数
self.offset += 1 #页数记得自增
print("-> 正在爬取第 %s 页..."% str(self.offset))#输出测试
new_url = 'http://'+ self.user_id +'.blog.hexun.com/p'+ str(self.offset) +'/default.html'#拼接分页的 url
yield scrapy.Request(new_url, callback = self.parse, headers=headers)#回调方法处理的是请求之后的数据
到这里我们再运行测试时发现可以正常全部数据数据(在这里不再截图展示),然后我们要把我们刚获取得的数据保存到本地文件中,那么我们就应该在实体管道文件 pipelines.py 中处理数据了:
import csv
class MyblogprojectPipeline(object):
def __init__(self):
self.f = open("blogs.csv", "w")
self.writer = csv.writer(self.f)
self.writer.writerow(['博文标题', '博文链接', '阅读数', '评论数'])
def process_item(self, item, spider):
#循环写入本地文件
for i in range(0, len(item["name"])):
#保存为csv文件
blog_list = [item["name"][i], item["url"][i], item["hits"][i], item["comment"][i]]
print(blog_list)#输出测试
self.writer.writerow(blog_list)
return item
def close_spider(self, spider):
self.f.close()
这里就要注意了,要使管道文件有效,我们就必须在配置文件 settings.py 中将它开启(这里值得一提的是管道配置文件的位置最好不要改变):
#启动管道
ITEM_PIPELINES = {
'myblogproject.pipelines.MyblogprojectPipeline': 300,
}
网友评论