1. 程式人生 > >Python爬蟲從入門到放棄(十一)之 Scrapy框架整體的一個了解

Python爬蟲從入門到放棄(十一)之 Scrapy框架整體的一個了解

object 定義 roc encoding eth obi pipe pos 等等

這裏是通過爬取伯樂在線的全部文章為例子,讓自己先對scrapy進行一個整理的理解

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

註:這個文章並不會對詳細的用法進行講解,是為了讓對scrapy各個功能有個了解,建立整體的印象。

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

分析要爬去的目標站信息

先看如下圖,首先我們要獲取下圖中所有文章的連接,然後是進入每個文章連接爬取每個文章的詳細內容。
每個文章中需要爬取文章標題,發表日期,以及標簽,贊賞收藏,評論數,文章內容。

技術分享

技術分享

技術分享

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

我們對這個爬蟲進行一個思路整理,通過如下圖表示:

技術分享

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

Scrapy的初步認識

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

技術分享

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

那麽如何創建上述的目錄,通過下面命令:

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

Python爬蟲從入門到放棄(十一)之 Scrapy框架整體的一個了解