爬取文章详情

parse包

urljoin 用于拼接url连接
join 用于将数组处理成带间隔的字符串

  1. ",".join(tag_list)
  2. parse.urljoin(response.url, post_url)

特殊分析
通过下面两种结果可以发现,如果待拼接连接没有/斜杠的话,会直接拼接在url后面,如果有斜杠则会直接拼接在域名后面(这是浏览器机制决定的)

  1. url = parse.urljoin("www.baidu.com/hi", "NewsAjax/GetAjaxNewsInfo")
  2. 结果:www.baidu.com/hi/NewsAjax/GetAjaxNewsInfo
  3. url = parse.urljoin("www.baidu.com/hi", "/NewsAjax/GetAjaxNewsInfo")
  4. 结果:www.baidu.com/NewsAjax/GetAjaxNewsInfo

Requests包(request请求)

request请求是同步的 , 它的作用和前后端交互的Ajax一致,在回调调用的函数中,通过response可以获得request的请求参数
url 连接
meta 请求参数
callback 回调函数
请求完成以后,会调用回调函数,这边注意不能写括号,写括号就是赋返回值了,但是不写就是赋值这个方法,我们想要的是赋值这个方法,而不是返回值,所以不加括号
应用场景:
scrapy是异步的,request是同步的,这样高并发场景肯定不行。我们怎么让request也变成异步的,通过yield

yield

和return对比学习,return是直接返回并结束,yield也返回,但它不会立马结束该函数,而是会把程序全部执行完才结束

  1. from urllib import parse
  2. import scrapy
  3. from scrapy import Request
  4. def parse(self, response):
  5. # 拿到每个父节点
  6. post_nodes = response.css("#news_list .news_block")
  7. # 通过for循环挨个获取父节点
  8. for post_node in post_nodes:
  9. # 获取每个父节点下的图片url
  10. image_url = post_node.css('.entry_summary a img::attr(href)').extract_first("")
  11. post_url = post_node.css("h2 a::attr(href)").extract_first("")
  12. yield Request(url=parse.urljoin(response.url, post_url),meta={"front_image_url":image_url})
  13. pass

生成器

生成器的一大特征就是yield 生成器是一个可迭代的对象,可以理解成一个可以终止的函数,它是可以迭代的那么我们可以使用迭代器来对它进行一个遍历,next()函数,指针概念

  1. #生成器
  2. def myGen():
  3. yield 1
  4. yield 2
  5. yield 3
  6. return 4
  7. print(myGen())
  8. for data in myGen():
  9. print(data)
  10. 结果:
  11. <generator object myGen at 0x10f7035f0>
  12. 1
  13. 2
  14. 3
  1. mygen = myGen();
  2. print(next(mygen))
  3. print(next(mygen))
  4. 结果
  5. 1
  6. 2

调试 (安全)

如果我们用pycharm不断的debug调试的话,可能会引发目标网站的系统监测,有没有不引发多访问的情况下,又能调试。那么我们就要使用shell命令,shell命令只会单次访问,我们用shell命令调试出来了直接把代码粘贴到代码中就可以,使用:scrapy shell命令

  1. scrapy shell 页面

案例

  1. (venv) user3@user3deMacBook-Pro cnblog % scrapy shell https://news.cnblogs.com/n/675408/

输入命令以后,scrapy给了我们一些可用的对象
image.png
着重说一下response,因为response页面就是整个单页的html元素,所以我们针对这个页面进行调试,把调试结果放进spider代码中就可以
image.png
这样我们只需要访问一次网页,就可以针对该单页重复调试,而不像在编译器中,每次调试都要访问页面。

特殊爬取场景

Js生成的数据
如果页面某个内容是由Js生成的,那么我们就不能直接用选择器获取。使用request请求
image.png

  1. pip install -i https://pypi.douban.com/simple requests
  1. >>> response = requests.get("https://news.cnblogs.com/NewsAjax/GetAjaxNewsInfo?contentId=675408")
  2. >>> response
  3. <Response [200]>
  4. >>> response.text
  5. '{"ContentID":675408,"CommentCount":1,"TotalView":103,"DiggCount":0,"BuryCount":0}'
  6. >>> import json
  7. >>> json.loads(response.text)
  8. {'ContentID': 675408, 'CommentCount': 1, 'TotalView': 103, 'DiggCount': 0, 'BuryCount': 0}
  9. >>> j_data = json.loads(response.text)
  10. >>> j_data['TotalView']
  11. 103

图片

Requests包

  1. pip install -i https://pypi.douban.com/simple requests
  1. # 发起请求
  2. request.get("接口地址")

https://news.cnblogs.com/NewsAjax/GetAjaxNewsInfo?contentId=675408
案例

  1. >>> response = requests.get("https://news.cnblogs.com/NewsAjax/GetAjaxNewsInfo?contentId=675408")
  2. >>> response
  3. <Response [200]>
  4. >>> response.text
  5. '{"ContentID":675408,"CommentCount":1,"TotalView":103,"DiggCount":0,"BuryCount":0}'
  6. >>> import json
  7. >>> json.loads(response.text)
  8. {'ContentID': 675408, 'CommentCount': 1, 'TotalView': 103, 'DiggCount': 0, 'BuryCount': 0}
  9. >>> j_data = json.loads(response.text)
  10. >>> j_data['TotalView']
  11. 103

完整爬取详情案例

  1. import json
  2. from urllib import parse
  3. import scrapy
  4. from scrapy import Request
  5. import requests
  6. import re
  7. class BlogspiderSpider(scrapy.Spider):
  8. name = 'blogSpider'
  9. allowed_domains = ['news.cnblogs.com']
  10. start_urls = ['http://news.cnblogs.com/']
  11. def parse(self, response):
  12. post_nodes = response.css("#news_list .news_block")[:1]
  13. for post_node in post_nodes:
  14. image_url = post_node.css('.entry_summary a img::attr(href)').extract_first("")
  15. post_url = post_node.css("h2 a::attr(href)").extract_first("")
  16. yield Request(url=parse.urljoin(response.url, post_url), meta={"front_image_url": image_url},
  17. callback=self.parse_detail)
  18. def parse_detail(self, response):
  19. match_re = re.match(".*?(\d+)", response.url)
  20. if match_re:
  21. title = response.xpath('//*[@id="news_title"]/a/text()').extract_first("")
  22. create_date = response.xpath('//*[@id="news_info"]/span[2]').extract_first("")
  23. content = response.xpath('//*[@id="news_content"]').extract_first("")
  24. tag_list = response.xpath('//*[@id="link_source2"]/text()').extract()
  25. tag = ",".join(tag_list)
  26. totalView = response.xpath('//*[@id="News_TotalView"]')
  27. post_id = match_re.group(1)
  28. yield Request(url=parse.urljoin(response.url, "/NewsAjax/GetAjaxNewsInfo?contentId={}".format(post_id)),
  29. callback=self.parse_nums)
  30. pass
  31. def parse_nums(self, response):
  32. j_data = json.loads(response.text)
  33. praise_nums = j_data["DiggCount"]
  34. fav_nums = j_data["TotalView"]
  35. comment_nums = j_data["CommentCount"]
  36. pass