一、scrapy安裝和配置
1、pip install scrapy
很多安裝容易出錯的包,可以直接下載安裝https://www.lfd.uci.edu/~gohike/pythonlibs
2、scrapy web抓取框架,底部異步io框架,事件循環+回調模式。儘量不要使用同步io。
3、常見命令:
scrapy startproject AricleSpider 創建scrapy項目
4、目錄結構
spiders
items
middlewares
pipelines
settings
5、創建自定義spider
scrapy genspider jobbole news.cnblogs.com
二、pycharm調試scrapy
1、scrapy crawl jobbole 啓動爬蟲
或者:
腳本啓動爬蟲:代碼中啓動根目錄建main.py
from scrapy.cmdline import execute
sys.path.append(os.path.dirname(__file__))
execute(["scrapy","crawl","jobbole"])
2、啓動程序後調用start_urls 調用parse()
三、xpath語法
1、節點關係
article 選取所有元素的所有子節點
/article 選取根元素article
article/a article的子元素a元素
//div 所有div子元素(無論出現在哪裏)
article//div article所有後代div,無論出現在哪裏
//@class 所有名爲class的屬性
/article/div[1] article子元素第一個div元素
/article/div[last()] 子元素第一個div元素
/article/div[last()-1] 屬於article子元素的倒數第二個div元素
/article[@lang] 選取擁有lang屬性的div元素
//div[@lang='eng'] 選取所以lang屬性爲eng的div元素
/div/* div元素的所有子節點
//* 所有元素
//div[@*] 所有帶屬性的title元素
/div/a|//div/p 選取所有div元素的a和p元素
//span|//ul 選取文檔總span和ul元素
article/div/p|//span 屬於article元素的div元素的p元素和所有span元素
2、scrapy中通過xpath獲取需要的元素
selectorList = response.xpath('xxx').extract_first("")
四、css選擇器(scrapy支持xpath和css選擇器)
*所有節點
#container 選擇id爲container的節點
.container 所有class包含container的節點
li a 所有li下所有a節點
ul + p ul後面第一個p元素
div#container > ul id爲container的div的ul子元素
p~ul 前面有p元素的每個ul元素
a[title] 所有屬性爲title的a元素
a[href="http://jobbole.com"] 選取所有href屬性爲jobbole.com的a元素
a[href*="jobole"] 選取所有href屬性包含jobbole的暗元素
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
2、使用方式:response.css('div#news_list h2 a::attr(href)').extract_first("")
3、如果沒有response怎麼使用xpath和css選擇器?
from scrapy import Selector
sel = Selector(test=response.text)
url = sel.css('div#news_list h2 a::attr(href)').extract()
五、爬蟲抓取
1、parse 一般做抓取策略,並不做解析 (這裏獲取列表中新聞url,並交給scrapy下載後調用解析方法)
2、response.css 返回時Selector 可直接調用css。
3、parse.urljoin(response.url,post_url) 如果請求地址是完整的url不會提取域名,如果不是會提取url域名。
4、parse裏 yield Request(url=parse.urljoin(response.url,post_url),meta={"front_image_url":image_url},callback=parse_detail)
5、源碼中start_requests裏 有dont_filter,會去重,可以設置不過濾。
6、scrapy是異步框架,下載完成後才進入parse_detail
六、提取頁面詳情頁
1、scrapy shell 測試使用。如scrapy shell https://news.cnblogs.com/n/643059 ,可以直接用xpath測試
2、有些數據在html裏提取不到,可能是js加載,需要通過請求查找。
3、儘量不要使用同步庫,如requests,可以通過yield 把請求發送出去(回調模式)
4、傳遞參數,通過meta傳遞
七、scrapy數據傳遞方式:
items:默認生成範例,可以寫item,繼承scrapy.Item,可以簡單理解爲dict。
定義需要解析的字段,如:title = scrapy.Field() 可以理解爲這裏的字段都會存儲在數據庫內。
yield只能 返回Request(走下載邏輯)和Item(走pipline),
八、settings裏的ROBOTSTXT_OBEY如果設置爲True,則網站的robots里約束的不會爬,一般改爲Fales
settings裏有pipeline,一般註釋,可以打開,數字是設置優先級,越小越先執行。打開後會進入pipeline,items定義的值會帶過來,所以這裏可以做數據保存和處理。
九、如果讓scrapy自動下載文件?
settings加配置:
1、'scrapy.pipelines.images.ImagesPipeline': 1,
2、增加配置路徑:IMAGES_STORE = os.path.join(os.path.dirname(os.path.abspath(__file__)),'images')
3、IMAGES_URLS_FIELD = 'front_image_url' 圖片路徑名的配置
4、front_image_url必須是一個list
如果需要存儲對圖片本地路徑進行存儲,可以重寫pipline
class AricleImagePipeline(ImagesPipeline):
# 把下載圖片和對應的本地地址放在一個對象中
def item_completed(self, results, item, info):
if 'front_image_url' in item:
for ok,value in results:
# value 存放圖片url和本地存儲path
image_file_path = value["path"]
item["front_image_path"] = image_file_path
return item
並修改settings
十、把數據保存在本地json
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()
十一、scrapy自帶 存儲json的方法:
class JsonExporterPipline(object):
# 系統方法導出json數據
def __init__(self):
self.file = codecs.open('articleexport.json', 'wb', encoding='utf-8')
self.exporter = JsonItemExporter(self.file,encoding='utf-8',ensure_ascii=False)
self.export.start_exporting()
def process_item(self,item,spider):
self.exporter.export_item(item)
return item
def spider_closed(self, spider):
self.exporter.finish_exporting()
self.file.close()
十二、同步入庫mysql
class MysqlPipline(object):
def __init__(self):
self.conn = MySQLdb.connect('127.0.0.1', 'root', 'root', 'article_spider', charset='utf8', use_unicode=True)
self.cursor = self.conn.cursor()
def process_item(self, item, spider):
insert_sql = '''
insert into jobbole_article(title,url,url_object_id,front_image_url,front_image_path,parise_nums,comment_nums,fav_nums,tags,content,creat_date)
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
'''
params = list()
params.append(item.get('title'))
params.append(item.get('url'))
params.append(item.get('url_object_id'))
from_image = ','.join(item.get('front_image_url',''))
params.append(from_image)
params.append(item.get('front_image_path'))
params.append(item.get('parise_nums',0))
params.append(item.get('comment_nums',0))
params.append(item.get('fav_nums',0))
params.append(item.get('tags',''))
params.append(item.get('content',''))
params.append(item.get('creat_date','1970-07-01'))
self.cursor.execute(insert_sql,tuple(params))
self.conn.commit()
return item
十三、異步入庫mysql(通用方式,可以直接處理)
class MysqlTwistedPipline(object):
def __init__(self, dbpool):
self.dbpool = dbpool
@classmethod
def from_settings(cls, settings):
from MySQLdb.cursors import DictCursor
dbparms = dict(
host=settings['MYSQL_HOST'],
db=settings['MYSQL_DBNAME'],
user=settings['MYSQL_USER'],
passwd=settings['MYSQL_PASSWORD'],
charset='utf8',
cursorclass=DictCursor,
use_unicode=True
)
dbpool = adbapi.ConnectionPool('MySQLdb', **dbparms)
return cls(dbpool)
def process_item(self, item, spider):
query = self.dbpool.runInteraction(self.do_insert, item)
query.addErrback(self.handle_error, item, spider)
def handle_error(self, failure, item, spider):
print(failure)
def do_insert(self, cursor, item):
insert_sql = '''
insert into jobbole_article(title,url,url_object_id,front_image_url,front_image_path,parise_nums,comment_nums,fav_nums,tags,content,creat_date)
values (%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s)
'''
params = list()
params.append(item.get('title'))
params.append(item.get('url'))
params.append(item.get('url_object_id'))
from_image = ','.join(item.get('front_image_url', ''))
params.append(from_image)
params.append(item.get('front_image_path'))
params.append(item.get('parise_nums', 0))
params.append(item.get('comment_nums', 0))
params.append(item.get('fav_nums', 0))
params.append(item.get('tags', ''))
params.append(item.get('content', ''))
params.append(item.get('creat_date', '1970-07-01'))
cursor.execute(insert_sql, tuple(params))
十四、數據庫主鍵衝突處理方式:
添加on DUPLICATE KEY UPDATE
INSERT INTO jobbole_article
(front_image_url, create_date,image_url_id,title,content,tags,content_id,comment_count,total_view,digg_count,bury_count)
VALUES
("{}","{}","{}","{}",'{}',"{}",{},{},{},{},{}) on DUPLICATE KEY UPDATE parise_nums=VALUES(bury_count);;
十五、ItemLoader用法,可以讓代碼簡潔