這裏是經過爬取伯樂在線的所有文章爲例子,讓本身先對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使用了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裏存放的是咱們要爬取數據的字段信息,代碼以下:
咱們分別要爬取的信息包括:文章標題,文件發佈時間,文章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爲主要的爬蟲代碼,包括了對頁面的請求以及頁面的處理,這裏有幾個知識點須要注意:
這些知識點我會在後面詳細寫一個文章整理,這裏先有一個初步的印象。
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
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