第三章 爬取伯乐在线

爬取伯乐在线

标签(空格分隔): python scrapy


项目创建

  • pycharm 本身是不会自带 scrapy 框架的
# 虚拟环境安装
mkvirtualenv --python=python地址 article_spider
# 跳转虚拟环境下 下载scrapy
pip install -i https://pypi.douban.com/simple/ scrapy
# 这里排个坑 我本地安装失败了 是由于twisted 有问题 访问下载 http://www.lfd.uci.edu/~gohlke/pythonlibs/
pip install 下载好的文件直接拖进来(C:\Users\JQ\Downloads\Twisted-17.9.0-cp35-cp35m-win_amd64.whl)
pip install -i https://pypi.douban.com/simple/ scrapy
# 创建项目 一般习惯项目路径 我这里放在e盘
workon article_spider
scrapy startproject ArticleSpider
# 创建文件 起始页 
scrapy genspider jobbole blog.jobbole.com
# 这里项目默认的环境设置


页面内容提取方式

  • 项目调试文件配置
main.py # 主项目目录下

from scrapy.cmdline import excute # 调试执行

import sys # 系统文件
import os # 内容

sys.path.append(os.path.dirname(os.path.abspath(__file__)))
excute(["scrapy","crawl","jobbole"])

  • 简介

    1.xpath使用路径表达式在xml和html中进行导航.

    2.xpath包含标准函数库.

    3.xpath是一个w3c的标准.

  • xpath选择器

表达式 说明
article 选取所有article元素的所有子节点
/article 选取根元素article
article/a 选取所有article元素的子节点a元素
//div 选取所有div元素不论出现在什么地方
article//div 选取所有article元素的后代div元素,不管在article下任何位置
//@class 选取所有名为class的属性
/article/div[1] 选取所有article子元素的第一代div元素
/article/div[last()] 选取article子元素的最后一个div元素
/article/div[last()-1] 选取article子元素的倒数第二个div元素
//div[@lang] 选取所有拥有lang属性的div元素
//div[@lang='eng'] 选取所有langs属性值为engde div元素
/div/* 选取所有div元素所有子节点
//* 选取所有元素
//div[@*] 选取带属性元素
//div/a//div/p 选取所有div元素的a和p元素
//span//ul] 选取span和ul元素
article/div/p//span 选取所有article元素的div的p元素以及文档中所有的span元素

注意上面后三个表格特殊符号|用\代替.

  • css选择器
表达式 说明
* 选择所有节点
#container 选择id为container的节点
.container 选取所有class包含container的节点
li a 选取li下的所有的a节点
ul+p 选择ul后面的第一个p元素
div#container>ul 选取id为container的div的第一个ul子元素
ul~p 选择与ul相邻所有p元素
a[title] 选择所有title属性的a元素
a[href="http://jobbole.com"] 选取所有href属性为jobbole.com值得a元素
a[href*="jobole"] 选取所有href属性包含jobbole的a元素
a[href^="http"] 选择所有href属性以http开头的a元素
a[href$=".jpg"] 选取所有href属性值以.jpg结尾的a元素
input[type=radio]:checked 选择选中的radio的元素
div:not(#container) 选择所有id非container的div元素
li:nth-child(3) 选择第三个li元素
tr:nth-child(2n) 第偶数个tr

注意提取内容用伪类选择器"::text".

伯乐在线单文页面提取

  • shell调试
scrapy shell http://blog.jobbole.com/112713/

  • xpath页面信息提取

title = response.xpath('//*[@id="post-112713"]/div[1]/h1/text()').extract()
create_time = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract()[0].strip().replace("·","").strip()
praise_nums = response.xpath('//*[contains(@class,"vote-post-up")]/h10/text()').extract()[0]
fav_nums = response.xpath('//*[contains(@class,"bookmark-btn")]/text()').extract()[0]

regex_nums = ".*?(\d+).*"
match_fav = re.match(regex_nums,fav_nums)
if match_fav:
    fav_nums = match_fav.group(1)
else:
    fav_nums = 0

comment_nums = response.xpath('//a[@href="#article-comment"]/span/text()').extract()[0]
match_comment = re.match(regex_nums,comment_nums)
if match_comment:
    comment_nums = match_comment.group(1)
else:
    comment_nums = 0

content = response.xpath('//div[@class="entry"]').extract()[0]
tag_list = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/a/text()').extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)
  • css页面信息提取
title = response.css('.entry-header h1::text').extract()[0]
create_time = response.css('.entry-meta-hide-on-mobile::text').extract()[0].strip().replace('·','').strip()
praise_nums = response.css('.vote-post-up h10::text').extract()[0]
fav_nums = response.css('.bookmark-btn::text').extract()[0]
mactch_fav = re.match(regex_nums,fav_nums)
if mactch_fav:
    fav_nums = mactch_fav.group(1)
else:
    fav_nums = 0
comment_nums = response.css("a[href='#article-comment'] span::text").extract()[0]
match_comment = re.match(regex_nums, comment_nums)
if match_comment:
    comment_nums = match_comment.group(1)
else:
    comment_nums = 0

content = response.css('div.entry').extract()[0]
tag_list = response.css('.entry-meta-hide-on-mobile a::text').extract()
tag_list = [element for element in tag_list if not element.strip().endswith("评论")]
tags = ",".join(tag_list)

提取伯乐在线所有文章

  • 逻辑实现:1. 获取文章的url并交给scrapy进行解析。2.获取下一页url并交给scrapy进行下载,下载后交给parse

  • request 访问并获取页面内容并通过yield进行下载

from scrapy.http import Request
Resquest(url=,collback=)
  • parse 对于href中链接不是全链接
from urllib import parse python3
import urlparse python2
parse.urljoin(response.url,post_url)

数据入库

  • item 类似于字典 但优于字典
class ArticleItem(scrapy.Item):
    title = scrapy.Filed()
    
  • 通过节点获取节点下的多个内容
 post_nodes = response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")
            yield Request(parse.urljoin(response.url,post_url),meta={"format_image_url":image_url},callback=self.parse_detail)
        next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_url:
            yield Request(parse.urljoin(response.url,next_url),callback=self.parse)

 # 封面图传递
fornat_image_url = response.meta.get("fornat_image_url","")
  • 图片保存
pip install -i https://pypi.douban.com/simple pillow

# 文章爬去py文件
article_items["title"] = title
        article_items["url"] = response.url
        article_items["create_time"] = create_time
        article_items["praise_nums"] = praise_nums
        article_items["fav_nums"] = fav_nums
        article_items["comment_nums"] = comment_nums
        article_items["content"] = content
        article_items["fornat_image_url"] = [fornat_image_url]
        article_items["tags"] = tags
        yield article_items # 这个很重要 保证内容进入管道
        
# setting 文件        
ITEM_PIPELINES = {
   'ArticleSpider.pipelines.ArticlespiderPipeline': 300,
    'scrapy.pipelines.images.ImagesPipeline':1, # 文件存储类库 1代表级别 越低则越靠前运行
}
IMAGES_URLS_FIELD = "fornat_image_url" # url链接地址
project_dir = os.path.abspath(os.path.dirname(__file__))
IMAGES_STORE = os.path.join(project_dir,'images') #文件存储位置

  • 存储图片路径存储
class ArticleImagePipeline(ImagesPipeline):
    # 重写提取文件存储路径
    def item_completed(self, results, item, info):
        for ok,value in results:
            image_file_path = value['path']
            item["fornat_image_path"] = image_file_path
        return item
  • url进行md5(摘要算法)压缩
def get_md5(url):
    if isinstance(url,str):
        url = url.encode("utf-8")
    m = hashlib.md5()
    m.update(url)
    return m.hexdigest()
  • 内容转换为json文件保存

    1.codecs 避免打开文件时出现编码错误

    2.json.dumps : dict转成str

    3.json.loads: str转成dict

    4.ensure_ascii=False: 避免处理英文以外语言时出错

    5.return item:交给下一个pipeline处理

# 自定义json文件导出
class JsonWithArticlePipeline(object):
    def __init__(self):
        self.file = codecs.open('article.json','w',encoding='utf-8')

    def process_item(self, item, spider):
        lines = json.dumps(dict(item),ensure_ascii= False)+'\n'
        self.file.write(lines)
        return item

    def spider_closed(self,spider):
        self.file.close()

# 调用scrapy的json文件导出
class JsonExporterPipeline(object):
    def __init__(self):
        self.file = open('articleexporter.json','wd')
        self.exporter = JsonItemExporter(self.file,encoding='utf-8',ensure_ascii = False)
        self.exporter.start_exporting()
    def closed_spider(self,spider):
        self.exporter.finish_exporting()
        self.file.close()
    def process_item(self, item, spider):
        self.exporter.export_item(item)
        return item
  • 内容入库

1.安装mysql驱动

pip install mysqlclient


# 会出现数据堵塞
class MysqlPipeline(object):
   def __init__(self):
       self.conn = MySQLdb.Connect("127.0.0.1","root","123456","article_spider",charset="utf8",use_unicode = True)
       self.cursor = self.conn.cursor()

   def process_item(self, item, spider):
       insert_sql = """
           insert into article(title,url,url_object_id,create_date,
           praise_nums,fav_nums,comment_nums,content,tags,fornat_image_url,
           fornat_image_path) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
       """
       self.cursor.execute(insert_sql,(item["title"],item["url"],item["url_object_id"],item["create_time"],
                                       item["praise_nums"],item["fav_nums"],item["comment_nums"],
                                       item["content"],item["tags"],item["fornat_image_url"],
                                       item["fornat_image_path"]))
       self.conn.commit()

2.数据堵塞解决方案

# 异步方式进行数据插入
class MysqlTwistedPipeline:
    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls,settings):
        dbparms = dict(
            host = settings["MYSQL_HOST"],
            db = settings["MYSQL_DBNAME"],
            user = settings["MYSQL_USER"],
            passwd = settings["MYSQL_PASSWORD"],
            charset = 'utf8',
            cursorclass = MySQLdb.cursors.DictCursor,
            use_unicode = True,
        )
        dbpool =  adbapi.ConnectionPool("MySQLdb",**dbparms)
        return cls(dbpool)

    def process_item(self, item, spider):
        query = self.dbpool.runInteraction(self.insert_sql,item)
        query.addErrback(self.handle_error) # 处理异常

    def handle_error(self,failure):
        print(failure)

    def insert_sql(self,cursor,item):
        insert_sql = """
                    insert into article(title,url,url_object_id,create_date,
                    praise_nums,fav_nums,comment_nums,content,tags,fornat_image_url,
                    fornat_image_path) values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
                """
        cursor.execute(insert_sql, (item["title"], item["url"], item["url_object_id"], item["create_time"],
                                         item["praise_nums"], item["fav_nums"], item["comment_nums"],
                                         item["content"], item["tags"], item["fornat_image_url"],
                                         item["fornat_image_path"]))


项目优化

  • itemloader

from scrapy.loader import ItemLoader

item_loader = ItemLoader(item = 字典,response )

item_loader.addcss("","") # 分别为字典 以及 提取类型
item_loader.addcss("","")
item_loader.addvalue("", ) # 这里则为获取的值


  • 原视频UP主慕课网(聚焦Python分布式爬虫必学框架Scrapy 打造搜索引擎)
  • 本篇博客撰写人: XiaoJinZi 个人主页 转载请注明出处
  • 学生能力有限 附上邮箱: [email protected] 不足以及误处请大佬指责

你可能感兴趣的:(第三章 爬取伯乐在线)