Python之爬蟲從入門到放棄(十三) Scrapy框架總體的瞭解

這裏是經過爬取伯樂在線的所有文章爲例子,讓本身先對scrapy進行一個整理的理解css

該例子中的詳細代碼會放到個人github地址:https://github.com/pythonsite/spider/tree/master/jobboleSpidernode

注:這個文章並不會對詳細的用法進行講解,是爲了讓對scrapy各個功能有個瞭解,創建總體的印象。python

在學習Scrapy框架以前,咱們先經過一個實際的爬蟲例子來理解,後面咱們會對每一個功能進行詳細的理解。
這裏的例子是爬取http://blog.jobbole.com/all-posts/ 伯樂在線的所有文章數據mysql

分析要爬去的目標站信息

先看以下圖,首先咱們要獲取下圖中全部文章的鏈接,而後是進入每一個文章鏈接爬取每一個文章的詳細內容。
每一個文章中須要爬取文章標題,發表日期,以及標籤,讚揚收藏,評論數,文章內容。git

 

 

 

 

 

對於該爬蟲的一個總體思路

咱們對這個爬蟲進行一個思路整理,經過以下圖表示:github

以上是咱們對這個爬蟲需求瞭解,下面咱們經過scrapy爬取咱們想要爬取的數據,下面咱們先對scrapy進行一個簡單的瞭解sql

Scrapy的初步認識

Scrapy使用了Twisted做爲框架,Twisted有些特殊的地方是它是事件驅動的,而且比較適合異步的代碼。對於會阻塞線程的操做包含訪問文件、數據庫或者Web、產生新的進程並須要處理新進程的輸出(如運行shell命令)、執行系統層次操做的代碼(如等待系統隊列),Twisted提供了容許執行上面的操做但不會阻塞代碼執行的方法。
scrapy的項目結構:shell

items.py 負責數據模型的創建,相似於實體類。
middlewares.py 本身定義的中間件。
pipelines.py 負責對spider返回數據的處理。
settings.py 負責對整個爬蟲的配置。
spiders目錄 負責存放繼承自scrapy的爬蟲類。
scrapy.cfg scrapy基礎配置數據庫

那麼如何建立上述的目錄,經過下面命令:json

zhaofandeMBP:python_project zhaofan$ scrapy startproject test1
New Scrapy project 'test1', using template directory '/Library/Frameworks/Python.framework/Versions/3.5/lib/python3.5/site-packages/scrapy/templates/project', created in:
    /Users/zhaofan/Documents/python_project/test1

You can start your first spider with:
    cd test1
    scrapy genspider example example.com
zhaofandeMBP:python_project zhaofan$ 
zhaofandeMBP:test1 zhaofan$ scrapy genspider shSpider hshfy.sh.cn
Created spider 'shSpider' using template 'basic' in module:
  test1.spiders.shSpider

相信上面這段話你確定會以爲很無聊,因此直接分析爬蟲代碼。

代碼的項目結構

items.py代碼分析

items.py裏存放的是咱們要爬取數據的字段信息,代碼以下:
咱們分別要爬取的信息包括:文章標題,文件發佈時間,文章url地址,url_object_id是咱們會對地址進行md5加密,front_image_url 是文章下圖片的url地址,front_image_path圖片的存放路徑

class JoBoleArticleItem(scrapy.Item):
    title = scrapy.Field()
    create_date = scrapy.Field()
    url = scrapy.Field()
    url_object_id = scrapy.Field()
    front_image_url = scrapy.Field()
    front_image_path = scrapy.Field()
    praise_nums = scrapy.Field()
    fav_nums = scrapy.Field()
    comment_nums = scrapy.Field()
    tag = scrapy.Field()
    content = scrapy.Field()

spiders/Article.py代碼分析

spiders目錄下的Article.py爲主要的爬蟲代碼,包括了對頁面的請求以及頁面的處理,這裏有幾個知識點須要注意:
這些知識點我會在後面詳細寫一個文章整理,這裏先有一個初步的印象。

1. 咱們爬取的頁面時http://blog.jobbole.com/all-posts/,因此parse的response,返回的是這個頁面的信息,可是咱們這個時候須要的是獲取每一個文章的地址繼續訪問,這裏就用到了yield Request()這種用法,能夠把獲取到文章的url地址繼續傳遞進來再次進行請求。
2. scrapy提供了response.css這種的css選擇器以及response.xpath的xpath選擇器方法,咱們能夠根據本身的需求獲取咱們想要的字段信息

class ArticleSpider(scrapy.Spider):
    name = "Article"
    allowed_domains = ["blog.jobbole.com"]
    start_urls = ['http://blog.jobbole.com/all-posts/']

    def parse(self, response):
        '''
        1.獲取文章列表也中具體文章url,並交給scrapy進行下載後並進行解析
        2.獲取下一頁的url並交給scrapy進行下載,下載完成後,交給parse
        :param response:
        :return:
        '''
        #解析列表頁中全部文章的url,並交給scrapy下載後進行解析
        post_nodes = response.css("#archive .floated-thumb .post-thumb a")
        for post_node in post_nodes:
            #image_url是圖片的地址
            image_url = post_node.css("img::attr(src)").extract_first("")
            post_url = post_node.css("::attr(href)").extract_first("")
            #這裏經過meta參數將圖片的url傳遞進來,這裏用parse.urljoin的好處是若是有域名我前面的response.url不生效
            # 若是沒有就會把response.url和post_url作拼接
            yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":parse.urljoin(response.url,image_url)},callback=self.parse_detail)

        #提取下一頁並交給scrapy下載
        next_url = response.css(".next.page-numbers::attr(href)").extract_first("")
        if next_url:
            yield Request(url=next_url,callback=self.parse)

    def parse_detail(self,response):
        '''
        獲取文章的詳細內容
        :param response:
        :return:
        '''
        article_item = JoBoleArticleItem()



        front_image_url = response.meta.get("front_image_url","")  #文章封面圖地址
        title = response.xpath('//div[@class="entry-header"]/h1/text()').extract_first()


        create_date = response.xpath('//p[@class="entry-meta-hide-on-mobile"]/text()').extract()[0].strip().split()[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("評論")]
        tag =",".join(tag_list)
        praise_nums = response.xpath('//span[contains(@class,"vote-post-up")]/h10/text()').extract()
        if len(praise_nums) == 0:
            praise_nums = 0
        else:
            praise_nums = int(praise_nums[0])
        fav_nums  = response.xpath('//span[contains(@class,"bookmark-btn")]/text()').extract()[0]
        match_re = re.match(".*(\d+).*",fav_nums)
        if match_re:
            fav_nums = int(match_re.group(1))
        else:
            fav_nums = 0

        comment_nums =response.xpath("//a[@href='#article-comment']/span/text()").extract()[0]
        match_com = re.match(".*(\d+).*",comment_nums)
        if match_com:
            comment_nums = int(match_com.group(1))
        else:
            comment_nums=0

        content = response.xpath('//div[@class="entry"]').extract()[0]


        article_item["url_object_id"] = get_md5(response.url) #這裏對地址進行了md5變成定長
        article_item["title"] = title
        article_item["url"] = response.url
        try:
            create_date = datetime.datetime.strptime(create_date,'%Y/%m/%d').date()
        except Exception as e:
            create_date = datetime.datetime.now().date()

        article_item["create_date"] = create_date
        article_item["front_image_url"] = [front_image_url]
        article_item["praise_nums"] = int(praise_nums)
        article_item["fav_nums"] = fav_nums
        article_item["comment_nums"] = comment_nums
        article_item["tag"] = tag
        article_item['content'] = content

        yield article_item
View Code

 

pipeline中代碼的分析

pipeline主要是對spiders中爬蟲的返回的數據的處理,這裏咱們可讓寫入到數據庫,也可讓寫入到文件等等。
下面代碼中主要包括的寫入到json文件以及寫入到數據庫,包括異步插入到數據庫,還有圖片的處理,這裏咱們能夠定義各類咱們須要的pipeline,固然這裏咱們不一樣的pipeline是有必定的順序的,須要的設置是在settings配置文件中,以下,後面的數字表示的是優先級,數字越小優先級越高。

 

class JobbolespiderPipeline(object):
    def process_item(self, item, spider):
        return item

class JsonWithEncodingPipeline(object):
    '''
    返回json數據到文件
    '''
    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()


class MysqlPipeline(object):
    '''
    插入mysql數據庫
    '''
    def __init__(self):
        self.conn =pymysql.connect(host='192.168.1.19',port=3306,user='root',passwd='123456',db='article_spider',use_unicode=True, charset="utf8")
        self.cursor = self.conn.cursor()

    def process_item(self,item,spider):
        insert_sql = '''
        insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
        '''

        self.cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))
        self.conn.commit()


class MysqlTwistedPipline(object):
    '''
    採用異步的方式插入數據
    '''
    def __init__(self,dbpool):
        self.dbpool = dbpool

    @classmethod
    def from_settings(cls,settings):
        dbparms = dict(
            host = settings["MYSQL_HOST"],
            port = settings["MYSQL_PORT"],
            user = settings["MYSQL_USER"],
            passwd = settings["MYSQL_PASSWD"],
            db = settings["MYSQL_DB"],
            use_unicode = True,
            charset="utf8",
        )
        dbpool = adbapi.ConnectionPool("pymysql",**dbparms)
        return cls(dbpool)
    def process_item(self,item,spider):
        '''
        使用twisted將mysql插入變成異步
        :param item:
        :param spider:
        :return:
        '''
        query = self.dbpool.runInteraction(self.do_insert,item)
        query.addErrback(self.handle_error)

    def handle_error(self,failure):
        #處理異步插入的異常
        print(failure)

    def do_insert(self,cursor,item):
        #具體插入數據
        insert_sql = '''
        insert into jobbole_article(title,create_date,url,url_object_id,front_image_url,front_image_path,comment_nums,fav_nums,praise_nums,tag,content) VALUES (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
        '''
        cursor.execute(insert_sql,(item["title"],item["create_date"],item["url"],item["url_object_id"],item["front_image_url"],item["front_image_path"],item["comment_nums"],item["fav_nums"],item["praise_nums"],item["tag"],item["content"]))



class ArticleImagePipeline(ImagesPipeline):
    '''
    對圖片的處理
    '''
    def item_completed(self, results, item, info):

        for ok ,value in results:
            if ok:
                image_file_path = value["path"]
                item['front_image_path'] = image_file_path
            else:
                item['front_image_path'] = ""


        return item
View Code
相關文章
相關標籤/搜索