craig 0.0.1.pre → 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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