craig 0.0.1.pre → 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (108) hide show
  1. data/README.md +165 -28
  2. data/Rakefile +7 -0
  3. data/craig.gemspec +2 -2
  4. data/lib/craig/categories.rb +169 -0
  5. data/lib/craig/cities.rb +262 -0
  6. data/lib/craig/craigslist.rb +49 -0
  7. data/lib/craig/listing.rb +73 -0
  8. data/lib/craig/node.rb +151 -0
  9. data/lib/craig/page.rb +50 -0
  10. data/lib/craig/tasks/update_cities.rb +95 -0
  11. data/lib/craig/version.rb +1 -1
  12. data/lib/craig.rb +24 -1
  13. data/test/community_events_test.rb +56 -0
  14. data/test/for_sale_cars_trucks_test.rb +38 -0
  15. data/test/for_sale_photo_video_test.rb +24 -0
  16. data/test/parsing_test.rb +119 -0
  17. data/test/personals_casual_encounters_test.rb +36 -0
  18. data/test/resumes_test.rb +24 -0
  19. data/test/sample_html/community_event-one-page_files/craigslist.css +1487 -0
  20. data/test/sample_html/community_event-one-page_files/formats.js +100 -0
  21. data/test/sample_html/community_event-one-page_files/jquery-1.7.2.js +632 -0
  22. data/test/sample_html/community_event-one-page_files/jquery.form-defaults.js +2 -0
  23. data/test/sample_html/community_event-one-page_files/toChecklist.js +47 -0
  24. data/test/sample_html/community_event-one-page_files/tocs.js +30 -0
  25. data/test/sample_html/community_events-one-page.html +294 -0
  26. data/test/sample_html/community_events.html +1077 -0
  27. data/test/sample_html/community_events_files/craigslist.css +1487 -0
  28. data/test/sample_html/community_events_files/formats.js +100 -0
  29. data/test/sample_html/community_events_files/jquery-1.7.2.js +632 -0
  30. data/test/sample_html/community_events_files/jquery-1.js +632 -0
  31. data/test/sample_html/community_events_files/jquery.form-defaults.js +2 -0
  32. data/test/sample_html/community_events_files/jquery.js +2 -0
  33. data/test/sample_html/community_events_files/toChecklist.js +47 -0
  34. data/test/sample_html/community_events_files/tocs.js +30 -0
  35. data/test/sample_html/for_sale.html +1478 -0
  36. data/test/sample_html/for_sale_cars_trucks.html +1483 -0
  37. data/test/sample_html/for_sale_cars_trucks_files/craigslist.css +1487 -0
  38. data/test/sample_html/for_sale_cars_trucks_files/formats.js +100 -0
  39. data/test/sample_html/for_sale_cars_trucks_files/jquery-1.7.2.js +632 -0
  40. data/test/sample_html/for_sale_cars_trucks_files/jquery.form-defaults.js +2 -0
  41. data/test/sample_html/for_sale_cars_trucks_files/toChecklist.js +47 -0
  42. data/test/sample_html/for_sale_cars_trucks_files/tocs.js +30 -0
  43. data/test/sample_html/for_sale_files/craigslist.css +1474 -0
  44. data/test/sample_html/for_sale_files/formats.js +107 -0
  45. data/test/sample_html/for_sale_files/jquery-1.js +632 -0
  46. data/test/sample_html/for_sale_files/jquery.js +2 -0
  47. data/test/sample_html/for_sale_files/toChecklist.js +47 -0
  48. data/test/sample_html/for_sale_files/tocs.js +30 -0
  49. data/test/sample_html/for_sale_photo_video.html +674 -0
  50. data/test/sample_html/for_sale_photo_video_files/craigslist.css +1487 -0
  51. data/test/sample_html/for_sale_photo_video_files/formats.js +100 -0
  52. data/test/sample_html/for_sale_photo_video_files/jquery-1.7.2.js +632 -0
  53. data/test/sample_html/for_sale_photo_video_files/jquery.form-defaults.js +2 -0
  54. data/test/sample_html/for_sale_photo_video_files/toChecklist.js +47 -0
  55. data/test/sample_html/for_sale_photo_video_files/tocs.js +30 -0
  56. data/test/sample_html/gigs.html +1047 -0
  57. data/test/sample_html/gigs_files/craigslist.css +1474 -0
  58. data/test/sample_html/gigs_files/formats.js +107 -0
  59. data/test/sample_html/gigs_files/jquery-1.js +632 -0
  60. data/test/sample_html/gigs_files/jquery.js +2 -0
  61. data/test/sample_html/gigs_files/toChecklist.js +47 -0
  62. data/test/sample_html/gigs_files/tocs.js +30 -0
  63. data/test/sample_html/housing.html +1406 -0
  64. data/test/sample_html/housing_vacation_rentals_files/craigslist.css +1474 -0
  65. data/test/sample_html/housing_vacation_rentals_files/formats.js +107 -0
  66. data/test/sample_html/housing_vacation_rentals_files/jquery-1.7.2.js +632 -0
  67. data/test/sample_html/housing_vacation_rentals_files/jquery.form-defaults.js +2 -0
  68. data/test/sample_html/housing_vacation_rentals_files/toChecklist.js +47 -0
  69. data/test/sample_html/housing_vacation_rentals_files/tocs.js +30 -0
  70. data/test/sample_html/jobs.html +1120 -0
  71. data/test/sample_html/jobs_files/craigslist.css +1474 -0
  72. data/test/sample_html/jobs_files/formats.js +107 -0
  73. data/test/sample_html/jobs_files/jquery-1.js +632 -0
  74. data/test/sample_html/jobs_files/jquery.js +2 -0
  75. data/test/sample_html/jobs_files/toChecklist.js +47 -0
  76. data/test/sample_html/jobs_files/tocs.js +30 -0
  77. data/test/sample_html/personals.html +1096 -0
  78. data/test/sample_html/personals_casual_encounters.html +1086 -0
  79. data/test/sample_html/personals_casual_encounters_files/craigslist.css +1487 -0
  80. data/test/sample_html/personals_casual_encounters_files/formats.js +100 -0
  81. data/test/sample_html/personals_casual_encounters_files/jquery-1.7.2.js +632 -0
  82. data/test/sample_html/personals_casual_encounters_files/jquery.form-defaults.js +2 -0
  83. data/test/sample_html/personals_casual_encounters_files/toChecklist.js +47 -0
  84. data/test/sample_html/personals_casual_encounters_files/tocs.js +30 -0
  85. data/test/sample_html/personals_strictly_platonic_files/craigslist.css +1474 -0
  86. data/test/sample_html/personals_strictly_platonic_files/formats.js +107 -0
  87. data/test/sample_html/personals_strictly_platonic_files/jquery-1.js +632 -0
  88. data/test/sample_html/personals_strictly_platonic_files/jquery.js +2 -0
  89. data/test/sample_html/personals_strictly_platonic_files/toChecklist.js +47 -0
  90. data/test/sample_html/personals_strictly_platonic_files/tocs.js +30 -0
  91. data/test/sample_html/resumes.html +811 -0
  92. data/test/sample_html/resumes_files/craigslist.css +1487 -0
  93. data/test/sample_html/resumes_files/formats.js +100 -0
  94. data/test/sample_html/resumes_files/jquery-1.7.2.js +632 -0
  95. data/test/sample_html/resumes_files/jquery.form-defaults.js +2 -0
  96. data/test/sample_html/resumes_files/toChecklist.js +47 -0
  97. data/test/sample_html/resumes_files/tocs.js +30 -0
  98. data/test/sample_html/services.html +1096 -0
  99. data/test/sample_html/services_files/craigslist.css +1487 -0
  100. data/test/sample_html/services_files/formats.js +100 -0
  101. data/test/sample_html/services_files/jquery-1.7.2.js +632 -0
  102. data/test/sample_html/services_files/jquery.form-defaults.js +2 -0
  103. data/test/sample_html/services_files/toChecklist.js +47 -0
  104. data/test/sample_html/services_files/tocs.js +30 -0
  105. data/test/sample_html/test.rb +309 -0
  106. data/test/services_test.rb +25 -0
  107. data/test/test_helper.rb +26 -0
  108. metadata +205 -9
data/README.md CHANGED
@@ -1,51 +1,188 @@
1
1
  # Craig
2
2
 
3
- A robust little Craigslist scraper.
3
+ A Craigslist listings scraper.
4
+
5
+ * Inspired by [gregstallings/craigslist](https://github.com/gregstallings/craigslist).
6
+ * A just-for-fun self-educational exercise.
4
7
 
5
8
  ## Installation
6
9
 
7
- Add this line to your application's Gemfile:
10
+ $ gem install craig
8
11
 
9
- gem 'craig'
12
+ ## Usage
10
13
 
11
- And then execute:
14
+ ~~~ ruby
15
+ require "craig"
16
+ ~~~
12
17
 
13
- $ bundle
18
+ Pass in the city and category:
14
19
 
15
- Or install it yourself as:
20
+ ~~~ ruby
21
+ listings = Craig.query(:austin, :for_sale)
22
+ ~~~
16
23
 
17
- $ gem install craig
24
+ Or narrow down your results with a subcategory:
25
+
26
+ ~~~ ruby
27
+ listings = Craig.query(:austin, :for_sale => :computers)
28
+ ~~~
18
29
 
19
- ## Usage
30
+ Returns up to 100 `Listing`s.
20
31
 
21
- require "craig"
32
+ ## Examples
22
33
 
23
- Let's check out some cars within our budget (that have images).
34
+ Let's check out some cars within our budget.
24
35
 
25
- vehicles = Craig.query(:austin, :for_sale => :cars_and_trucks)
26
- vehicles.select { |v|
27
- v.price.between?(6000, 8000) &&
28
- v.image? &&
29
- v.seller == :owner
30
- }
31
-
32
- #=> [<Listing>, <Listing>, <Listing>]
36
+ ~~~ ruby
37
+ vehicles = Craig.query(:austin, :for_sale => :cars_trucks)
38
+ vehicles.select { |v|
39
+ v.price.between?(6000, 8000) &&
40
+ v.image? &&
41
+ v.seller == :owner
42
+ }
43
+
44
+ #=> [<Listing>, <Listing>, <Listing>]
45
+ ~~~
33
46
 
34
47
  I'm about to move to Birmingham. I wonder where the kids my age are living.
35
48
 
36
- listings = Craig.query(:birmingham, :personals => :strictly_platonic)
37
- listings.select { |p|
38
- p.age < 30 &&
39
- }.map(&:location)
40
-
41
- #= ["Highland Ave", "Highland Ave", "Highland Ave"]
49
+ ~~~ ruby
50
+ listings = Craig.query(:birmingham, :personals => :strictly_platonic)
51
+ listings.select { |p|
52
+ p.age < 30
53
+ }.map(&:location)
54
+
55
+ #= ["Highland Ave", "Highland Ave", "Highland Ave"]
56
+ ~~~
42
57
 
43
58
  Oh wait, I left my trombone downtown last night. I wonder if anyone found it.
44
59
 
45
- listings = Craig.query(:austin, :community => :lost_and_found
46
- listings.any? { |listing| listing.title =~ /trombone/i }
47
-
48
- #=> true
60
+ ~~~ ruby
61
+ listings = Craig.query(:austin, :community => :lost_found
62
+ listings.any? { |listing| listing.title =~ /trombone/i }
63
+
64
+ #=> true
65
+ ~~~
66
+
67
+ Let's get the price of the most expensive Macbook that's sold by an owner
68
+ (instead of a dealer) on or near UT campus.
69
+
70
+ ~~~ ruby
71
+ Craig.query(:austin, :for_sale => :computer)
72
+ .select { |c|
73
+ c.seller == :owner &&
74
+ c.location[/campus/i] &&
75
+ c.title[/macbook/i]
76
+ }.sort_by { |c|
77
+ -c.price
78
+ }.first
79
+
80
+ # Output
81
+
82
+ #=> {
83
+ :url => "http://austin.craigslist.org/sys/3723418912.html"
84
+ :title => "Late 2011 Macbook Pro"
85
+ :seller => :owner
86
+ :price => 1050
87
+ :posted_at => #<Date: 2013-01-23 ((2456316j,0s,0n),+0s,2299161j)>
88
+ :location => "West campus"
89
+ :has_map? => false
90
+ :has_image? => true
91
+ :id => 3723418912
92
+ }
93
+ ~~~
94
+
95
+ ## Available Listing methods
96
+
97
+ The easiest way to see what listings respond to is to just send them `Listing#to_hash`:
98
+
99
+ ~~~ ruby
100
+ Craig.query(:new_york_city, :jobs).map(&:to_hash)
101
+ ~~~
102
+
103
+ Or just look at console output (`Listing#to_s` outputs the hash in string form):
104
+
105
+ ~~~ ruby
106
+ Craig.query(:new_york_city, :jobs)
107
+ ~~~
108
+
109
+ Or you can look in `listing.rb` to see the type of nodes that Listings have, then find the method each node defines in `node.rb`.
110
+
111
+ Since I rarely use Ruby Modules, this gem started out as an self-educational scraper concept where Nodes are composed of Nodes are composed of Nodes but I didn't get very far. So now the internal API is a little spread out. TODO...
112
+
113
+ ## Available cities and categories
114
+
115
+ The Craigslist website itself serves as documentation for this
116
+ gem's API.
117
+
118
+ * Cities: http://www.craigslist.org/about/sites/
119
+ * Categories: http://austin.craigslist.org/
120
+
121
+ Just downcase Craigslist's name for it and replace spaces and punctuation with underscores.
122
+
123
+ Here, let me translate some for you to be clear:
124
+
125
+ <table>
126
+ <tr>
127
+ <th colspan="2">Cities</th>
128
+ </tr>
129
+ <tr>
130
+ <th>How it appears on Craigslist</th><th>The symbol you pass into Craig</th>
131
+ </tr>
132
+ <tr>
133
+ <td>beaumont / port arthur</td>
134
+ <td>:beaumont_port_arthur</td>
135
+ </tr>
136
+ <tr>
137
+ <td>cambridge, UK</td>
138
+ <td>:cambridge_uk</td>
139
+ </tr>
140
+ <tr>
141
+ <td>nice / cote d'azur</td>
142
+ <td>:nice_cote_d_azur</td>
143
+ </tr>
144
+ <tr>
145
+ <th colspan="2">Categories/Subcategories</th>
146
+ </tr>
147
+ <tr>
148
+ <th>How it appears on Craigslist</th><th>The symbol you pass into Craig</th>
149
+ </tr>
150
+ <tr>
151
+ <td>for sale</td>
152
+ <td>:for_sale</td>
153
+ </tr>
154
+ <tr>
155
+ <td>cds/dvd/vhs </td>
156
+ <td>:cds_dvd_vhs</td>
157
+ </tr>
158
+ <tr>
159
+ <td>accounting+finance </td>
160
+ <td>:accounting_finance</td>
161
+ </tr>
162
+ </table>
163
+
164
+ ~~~ ruby
165
+ Craig.query(:nice_cote_d_azur, :for_sale => :cds_dvd_vhs)
166
+ ~~~
167
+
168
+ If you're still unsure, just check out Craig's internal lookup table.
169
+
170
+ ## Diverges from Craigslist homepage
171
+
172
+ While I've mostly modeled Craig's API after Craigslist's website, I've
173
+ diverged from the homepage where I felt it made sense.
174
+
175
+ For example, I prefer the "Show All" view where possible.
176
+
177
+ :community => :events goes to /eee instead of /eve.
178
+
179
+ This way you see all events instead of just events with
180
+ date ranges.
181
+
182
+ :for_sale => :cars_trucks goes to the owners+dealers listings
183
+
184
+ instead of the splash page that lets you choose
185
+ TODO: Add search API or expose API to specify this stuff.
49
186
 
50
187
  ## Contributing
51
188
 
data/Rakefile CHANGED
@@ -7,3 +7,10 @@ Rake::TestTask.new do |t|
7
7
  end
8
8
  desc "Run tests"
9
9
  task :default => :test
10
+
11
+ namespace :cities do
12
+ desc "Update Craig::CITIES using Craigslists' city directory"
13
+ task :update do
14
+ system("ruby lib/craig/tasks/update_cities.rb")
15
+ end
16
+ end
data/craig.gemspec CHANGED
@@ -8,8 +8,8 @@ Gem::Specification.new do |gem|
8
8
  gem.version = Craig::VERSION
9
9
  gem.authors = ["Dan Neumann"]
10
10
  gem.email = ["danrodneu@gmail.com"]
11
- gem.description = %q{a robust little Craigslist scraper that fetches listings.}
12
- gem.summary = %q{a robust little Craigslist scraper.}
11
+ gem.description = %q{A Craigslist listings scraper.}
12
+ gem.summary = %q{A Craigslist listings scraper.}
13
13
  gem.homepage = "http://github.com/danneu/craig"
14
14
 
15
15
  gem.files = `git ls-files`.split($/)
@@ -0,0 +1,169 @@
1
+ module Craig
2
+ CATEGORIES = {
3
+ 'community' => {
4
+ 'path' => 'ccc',
5
+ 'children' => {
6
+ 'activities' => 'act',
7
+ 'artists' => 'ats',
8
+ 'childcare' => 'kid',
9
+ 'general' => 'com',
10
+ 'groups' => 'grp',
11
+ 'pets' => 'pet',
12
+ 'events' => 'eee', # Diverges. Original: "eve"
13
+ 'lost_found' => 'laf',
14
+ 'musicians' => 'muc',
15
+ 'local news' => 'vnn',
16
+ 'politics' => 'pol',
17
+ 'rideshare' => 'rid',
18
+ 'volunteers' => 'vol',
19
+ 'classes' => 'cls'
20
+ }
21
+ },
22
+ 'personals' => {
23
+ 'children' => {
24
+ 'strictly_platonic' => 'stp',
25
+ 'women_seek_women' => 'w4w',
26
+ 'women_seeking_men' => 'w4m',
27
+ 'men_seeking_women' => 'm4w',
28
+ 'men_seeking_men' => 'm4m',
29
+ 'misc_romance' => 'msr',
30
+ 'casual_encounters' => 'cas',
31
+ 'missed_connections' => 'mis',
32
+ 'rants_and_raves' => 'rnr'
33
+ }
34
+ },
35
+ 'housing' => {
36
+ 'path' => 'hhh',
37
+ 'children' => {
38
+ 'apts_housing' => 'apa',
39
+ 'rooms_shared' => 'roo',
40
+ 'sublets_temporary' => 'sub',
41
+ 'housing_wanted' => 'hsw',
42
+ 'housing_swap' => 'swp',
43
+ 'vacation_rentals' => 'vac',
44
+ 'parking_storage' => 'prk',
45
+ 'office_commercial' => 'off',
46
+ 'real_estate_for_sale' => 'rea'
47
+ }
48
+ },
49
+ 'for_sale' => {
50
+ 'path' => 'sss',
51
+ 'children' => {
52
+ 'appliances' => 'ppa',
53
+ 'antiques' => 'ata',
54
+ 'barter' => 'bar',
55
+ 'bikes' => 'bia',
56
+ 'boats' => 'boo',
57
+ 'books' => 'bka',
58
+ 'business' => 'bfa',
59
+ 'computer' => 'sya',
60
+ 'free' => 'zip',
61
+ 'furniture' => 'fua',
62
+ 'general' => 'foa',
63
+ 'jewelry' => 'jwa',
64
+ 'materials' => 'maa',
65
+ 'rvs' => 'rva',
66
+ 'sporting' => 'sga',
67
+ 'tickets' => 'tia',
68
+ 'tools' => 'tla',
69
+ 'wanted' => 'wan',
70
+ 'arts_crafts' => 'ara',
71
+ 'auto_parts' => 'pta',
72
+ 'baby_kids' => 'baa',
73
+ 'beauty_hlth' => 'haa',
74
+ 'cars_trucks' => 'cta', # Diverges. Original: "/i/autos"
75
+ 'cds_dvd_vhs' => 'ema',
76
+ 'cell_phones' => 'moa',
77
+ 'clothes_acc' => 'cla',
78
+ 'collectibles' => 'cba',
79
+ 'electronics' => 'ela',
80
+ 'farm_garden' => 'gra',
81
+ 'garage_sale' => 'gms',
82
+ 'household' => 'hsa',
83
+ 'motorcycles' => 'mca',
84
+ 'music_instr' => 'msa',
85
+ 'photo_video' => 'pha',
86
+ 'toys_games' => 'taa',
87
+ 'video_gaming' => 'vga'
88
+ }
89
+ },
90
+ 'services' => {
91
+ 'path' => 'bbb',
92
+ 'children' => {
93
+ 'beauty' => 'brs',
94
+ 'creative' => 'crs',
95
+ 'computer' => 'cps',
96
+ 'cycle' => 'cys',
97
+ 'event' => 'evs',
98
+ 'financial' => 'fns',
99
+ 'legal' => 'lgs',
100
+ 'lessons' => 'lss',
101
+ 'marine' => 'mas',
102
+ 'pet' => 'pas',
103
+ 'automotive' => 'aos',
104
+ 'farm_garden' => 'fgs',
105
+ 'household' => 'hss',
106
+ 'labor_move' => 'lbs',
107
+ 'skilld_trade' => 'sks',
108
+ 'real_estate' => 'rts',
109
+ 'sm_biz_ads' => 'biz',
110
+ 'therapeutic' => 'thp',
111
+ 'travel_vac' => 'trv',
112
+ 'write_ed_tr8' => 'wet'
113
+ }
114
+ },
115
+ 'jobs' => {
116
+ 'path' => 'jjj',
117
+ 'children' => {
118
+ 'accounting_finance' => 'acc',
119
+ 'admin_office' => 'ofc',
120
+ 'arch_engineering' => 'egr',
121
+ 'art_media_design' => 'med',
122
+ 'biotech_science' => 'sci',
123
+ 'business_mgmt' => 'bus',
124
+ 'customer_service' => 'csr',
125
+ 'education' => 'edu',
126
+ 'food_bev_hosp' => 'fbh',
127
+ 'general_labor' => 'lab',
128
+ 'government' => 'gov',
129
+ 'human_resources' => 'hum',
130
+ 'internet_engineers' => 'eng',
131
+ 'legal_paralegal' => 'lgl',
132
+ 'manufacturing' => 'mnu',
133
+ 'marketing_pr_ad' => 'mar',
134
+ 'medical_health' => 'hea',
135
+ 'nonprofit_sector' => 'npo',
136
+ 'real_estate' => 'rej',
137
+ 'retail_wholesale' => 'ret',
138
+ 'sales_biz_dev' => 'sls',
139
+ 'salon_spa_fitness' => 'spa',
140
+ 'security' => 'sec',
141
+ 'skilled_trade_craft' => 'trd',
142
+ 'software_qa_dba' => 'sof',
143
+ 'systems_network' => 'sad',
144
+ 'technical_support' => 'tch',
145
+ 'transport' => 'trp',
146
+ 'tv_film_video' => 'tfr',
147
+ 'web_info_design' => 'web',
148
+ 'writing_editing' => 'wri',
149
+ 'etc' => 'etc'
150
+ }
151
+ },
152
+ 'gigs' => {
153
+ 'path' => 'ggg',
154
+ 'children' => {
155
+ 'crew' => 'cwg',
156
+ 'event' => 'evg',
157
+ 'labor' => 'lbg',
158
+ 'talent' => 'tlg',
159
+ 'computer' => 'cpg',
160
+ 'creative' => 'crg',
161
+ 'domestic' => 'dmg',
162
+ 'writing' => 'wrg'
163
+ }
164
+ },
165
+ 'resumes' => {
166
+ 'path' => 'res'
167
+ }
168
+ }
169
+ end
@@ -0,0 +1,262 @@
1
+ # Generated by update_cities.rb
2
+ # Date: 22 Jan 2013
3
+ #
4
+ # Total Craigslist cities: 460
5
+ # Hash size: 252
6
+
7
+ module Craig
8
+ CITIES = {
9
+ "birmingham" => "bham",
10
+ "florence_muscle_shoals" => "shoals",
11
+ "gadsden_anniston" => "gadsden",
12
+ "huntsville_decatur" => "huntsville",
13
+ "anchorage_mat_su" => "anchorage",
14
+ "kenai_peninsula" => "kenai",
15
+ "southeast_alaska" => "juneau",
16
+ "flagstaff_sedona" => "flagstaff",
17
+ "mohave_county" => "mohave",
18
+ "fayetteville" => "fayar",
19
+ "fresno_madera" => "fresno",
20
+ "hanford_corcoran" => "hanford",
21
+ "humboldt_county" => "humboldt",
22
+ "imperial_county" => "imperial",
23
+ "mendocino_county" => "mendocino",
24
+ "monterey_bay" => "monterey",
25
+ "san_francisco_bay_area" => "sfbay",
26
+ "san_luis_obispo" => "slo",
27
+ "siskiyou_county" => "siskiyou",
28
+ "ventura_county" => "ventura",
29
+ "visalia_tulare" => "visalia",
30
+ "colorado_springs" => "cosprings",
31
+ "eastern_co" => "eastco",
32
+ "fort_collins_north_co" => "fortcollins",
33
+ "high_rockies" => "rockies",
34
+ "western_slope" => "westslope",
35
+ "eastern_ct" => "newlondon",
36
+ "northwest_ct" => "nwct",
37
+ "washington" => "washingtondc",
38
+ "daytona_beach" => "daytona",
39
+ "florida_keys" => "keys",
40
+ "ft_myers_sw_florida" => "fortmyers",
41
+ "heartland_florida" => "cfl",
42
+ "north_central_fl" => "lakecity",
43
+ "okaloosa_walton" => "okaloosa",
44
+ "sarasota_bradenton" => "sarasota",
45
+ "south_florida" => "miami",
46
+ "tampa_bay_area" => "tampa",
47
+ "treasure_coast" => "treasure",
48
+ "albany" => "albanyga",
49
+ "athens" => "athensga",
50
+ "columbus" => "columbusga",
51
+ "macon_warner_robins" => "macon",
52
+ "northwest_ga" => "nwga",
53
+ "savannah_hinesville" => "savannah",
54
+ "hawaii" => "honolulu",
55
+ "lewiston_clarkston" => "lewiston",
56
+ "bloomington_normal" => "bn",
57
+ "champaign_urbana" => "chambana",
58
+ "la_salle_co" => "lasalle",
59
+ "mattoon_charleston" => "mattoon",
60
+ "southern_illinois" => "carbondale",
61
+ "springfield" => "springfieldil",
62
+ "western_il" => "quincy",
63
+ "lafayette_west_lafayette" => "tippecanoe",
64
+ "muncie_anderson" => "muncie",
65
+ "richmond" => "richmondin",
66
+ "south_bend_michiana" => "southbend",
67
+ "southeast_ia" => "ottumwa",
68
+ "waterloo_cedar_falls" => "waterloo",
69
+ "manhattan" => "ksu",
70
+ "northwest_ks" => "nwks",
71
+ "southeast_ks" => "seks",
72
+ "southwest_ks" => "swks",
73
+ "bowling_green" => "bgky",
74
+ "eastern_kentucky" => "eastky",
75
+ "western_ky" => "westky",
76
+ "central_louisiana" => "cenla",
77
+ "southern_maryland" => "smd",
78
+ "western_maryland" => "westmd",
79
+ "cape_cod_islands" => "capecod",
80
+ "western_massachusetts" => "westernmass",
81
+ "worcester_central_ma" => "worcester",
82
+ "central_michigan" => "centralmich",
83
+ "detroit_metro" => "detroit",
84
+ "jackson" => "jxn",
85
+ "monroe" => "monroemi",
86
+ "northern_michigan" => "nmi",
87
+ "saginaw_midland_baycity" => "saginaw",
88
+ "southwest_michigan" => "swmi",
89
+ "the_thumb" => "thumb",
90
+ "upper_peninsula" => "up",
91
+ "duluth_superior" => "duluth",
92
+ "minneapolis_st_paul" => "minneapolis",
93
+ "rochester" => "rmn",
94
+ "southwest_mn" => "marshall",
95
+ "gulfport_biloxi" => "gulfport",
96
+ "north_mississippi" => "northmiss",
97
+ "southwest_ms" => "natchez",
98
+ "columbia_jeff_city" => "columbiamo",
99
+ "lake_of_the_ozarks" => "loz",
100
+ "southeast_missouri" => "semo",
101
+ "montana_old" => "montana",
102
+ "omaha_council_bluffs" => "omaha",
103
+ "scottsbluff_panhandle" => "scottsbluff",
104
+ "reno_tahoe" => "reno",
105
+ "new_hampshire" => "nh",
106
+ "central_nj" => "cnj",
107
+ "north_jersey" => "newjersey",
108
+ "clovis_portales" => "clovis",
109
+ "roswell_carlsbad" => "roswell",
110
+ "santa_fe_taos" => "santafe",
111
+ "elmira_corning" => "elmira",
112
+ "new_york_city" => "newyork",
113
+ "plattsburgh_adirondacks" => "plattsburgh",
114
+ "potsdam_canton_massena" => "potsdam",
115
+ "twin_tiers_ny_pa" => "twintiers",
116
+ "utica_rome_oneida" => "utica",
117
+ "eastern_nc" => "eastnc",
118
+ "hickory_lenoir" => "hickory",
119
+ "jacksonville" => "onslow",
120
+ "raleigh_durham_ch" => "raleigh",
121
+ "fargo_moorhead" => "fargo",
122
+ "north_dakota" => "nd",
123
+ "athens" => "athensohio",
124
+ "dayton_springfield" => "dayton",
125
+ "lima_findlay" => "limaohio",
126
+ "tuscarawas_co" => "tuscarawas",
127
+ "zanesville_cambridge" => "zanesville",
128
+ "northwest_ok" => "enid",
129
+ "corvallis_albany" => "corvallis",
130
+ "klamath_falls" => "klamath",
131
+ "medford_ashland" => "medford",
132
+ "altoona_johnstown" => "altoona",
133
+ "cumberland_valley" => "chambersburg",
134
+ "lehigh_valley" => "allentown",
135
+ "scranton_wilkes_barre" => "scranton",
136
+ "state_college" => "pennstate",
137
+ "rhode_island" => "providence",
138
+ "florence" => "florencesc",
139
+ "greenville_upstate" => "greenville",
140
+ "northeast_sd" => "nesd",
141
+ "pierre_central_sd" => "csd",
142
+ "rapid_city_west_sd" => "rapidcity",
143
+ "sioux_falls_se_sd" => "siouxfalls",
144
+ "south_dakota" => "sd",
145
+ "jackson" => "jacksontn",
146
+ "beaumont_port_arthur" => "beaumont",
147
+ "dallas_fort_worth" => "dallas",
148
+ "deep_east_texas" => "nacogdoches",
149
+ "del_rio_eagle_pass" => "delrio",
150
+ "killeen_temple_ft_hood" => "killeen",
151
+ "mcallen_edinburg" => "mcallen",
152
+ "odessa_midland" => "odessa",
153
+ "southwest_tx" => "bigbend",
154
+ "tyler_east_tx" => "easttexas",
155
+ "victoria" => "victoriatx",
156
+ "ogden_clearfield" => "ogden",
157
+ "provo_orem" => "provo",
158
+ "vermont" => "burlington",
159
+ "hampton_roads" => "norfolk",
160
+ "new_river_valley" => "blacksburg",
161
+ "southwest_va" => "swva",
162
+ "kennewick_pasco_richland" => "kpr",
163
+ "olympic_peninsula" => "olympic",
164
+ "pullman_moscow" => "pullman",
165
+ "seattle_tacoma" => "seattle",
166
+ "skagit_island_sji" => "skagit",
167
+ "spokane_coeur_d_alene" => "spokane",
168
+ "charleston" => "charlestonwv",
169
+ "eastern_panhandle" => "martinsburg",
170
+ "huntington_ashland" => "huntington",
171
+ "northern_panhandle" => "wheeling",
172
+ "parkersburg_marietta" => "parkersburg",
173
+ "southern_wv" => "swv",
174
+ "west_virginia_old" => "wv",
175
+ "appleton_oshkosh_fdl" => "appleton",
176
+ "kenosha_racine" => "racine",
177
+ "guam_micronesia" => "micronesia",
178
+ "u_s_virgin_islands" => "virgin",
179
+ "medicine_hat" => "hat",
180
+ "peace_river_country" => "peace",
181
+ "fraser_valley" => "abbotsford",
182
+ "kelowna_okanagan" => "kelowna",
183
+ "kootenays" => "cranbrook",
184
+ "skeena_bulkley" => "skeena",
185
+ "sunshine_coast" => "sunshine",
186
+ "st_john_s" => "newfoundland",
187
+ "brantford_woodstock" => "brantford",
188
+ "chatham_kent" => "chatham",
189
+ "hamilton_burlington" => "hamilton",
190
+ "kitchener_waterloo_cambridge" => "kitchener",
191
+ "london" => "londonon",
192
+ "niagara_region" => "niagara",
193
+ "ottawa_hull_gatineau" => "ottawa",
194
+ "sault_ste_marie" => "soo",
195
+ "prince_edward_island" => "pei",
196
+ "quebec_city" => "quebec",
197
+ "belgium" => "brussels",
198
+ "croatia" => "zagreb",
199
+ "finland" => "helsinki",
200
+ "brittany" => "rennes",
201
+ "loire_valley" => "loire",
202
+ "marseille" => "marseilles",
203
+ "nice_cote_d_azur" => "cotedazur",
204
+ "normandy" => "rouen",
205
+ "essen_ruhr" => "essen",
206
+ "greece" => "athens",
207
+ "florence_tuscany" => "florence",
208
+ "napoli_campania" => "naples",
209
+ "sicilia" => "sicily",
210
+ "venice_veneto" => "venice",
211
+ "amsterdam_randstad" => "amsterdam",
212
+ "norway" => "oslo",
213
+ "poland" => "warsaw",
214
+ "faro_algarve" => "faro",
215
+ "romania" => "bucharest",
216
+ "sweden" => "stockholm",
217
+ "turkey" => "istanbul",
218
+ "birmingham_west_mids" => "birmingham",
219
+ "cambridge_uk" => "cambridge",
220
+ "cardiff_wales" => "cardiff",
221
+ "devon_cornwall" => "devon",
222
+ "east_anglia" => "norwich",
223
+ "east_midlands" => "eastmids",
224
+ "newcastle_ne_england" => "newcastle",
225
+ "guam_micronesia" => "micronesia",
226
+ "chennai_madras" => "chennai",
227
+ "kolkata_calcutta" => "kolkata",
228
+ "surat_surat" => "surat",
229
+ "indonesia" => "jakarta",
230
+ "iran" => "tehran",
231
+ "iraq" => "baghdad",
232
+ "west_bank" => "ramallah",
233
+ "osaka_kobe_kyoto" => "osaka",
234
+ "beirut_lebanon" => "beirut",
235
+ "bicol_region" => "naga",
236
+ "cagayan_de_oro" => "cdo",
237
+ "taiwan" => "taipei",
238
+ "thailand" => "bangkok",
239
+ "united_arab_emirates" => "dubai",
240
+ "newcastle_nsw" => "ntl",
241
+ "tasmania" => "hobart",
242
+ "caribbean_islands" => "caribbean",
243
+ "bolivia" => "lapaz",
244
+ "rio_de_janeiro" => "rio",
245
+ "salvador_bahia" => "salvador",
246
+ "chile" => "santiago",
247
+ "dominican_republic" => "santodomingo",
248
+ "ecuador" => "quito",
249
+ "baja_california_sur" => "bajasur",
250
+ "ciudad_juarez" => "juarez",
251
+ "puerto_vallarta" => "pv",
252
+ "nicaragua" => "managua",
253
+ "peru" => "lima",
254
+ "venezuela" => "caracas",
255
+ "virgin_islands" => "virgin",
256
+ "egypt" => "cairo",
257
+ "ethiopia" => "addisababa",
258
+ "ghana" => "accra",
259
+ "morocco" => "casablanca",
260
+ "tunisia" => "tunis"
261
+ }
262
+ end